Haskell型族中的类型歧义

Haskell型族中的类型歧义,haskell,type-inference,type-families,Haskell,Type Inference,Type Families,我正在尝试将以下类Domain及其实例Domain {-# LANGUAGE TypeFamilies #-} data Transition = Transition class Domain d where type Set d type Engine d :: * -> * top :: Engine d (Set d) -- ... complement :: Set d -> Engine d (Set d) excl

我正在尝试将以下类
Domain
及其实例
Domain

{-# LANGUAGE TypeFamilies #-}

data Transition = Transition

class Domain d where
    type Set d
    type Engine d :: * -> *

    top :: Engine d (Set d)

    -- ...
    complement :: Set d -> Engine d (Set d)
    exclude    :: Set d -> Set d -> Engine d (Set d)
    -- ...

data TrivialDomain = TrivialDomain

instance Domain TrivialDomain where
    type Set TrivialDomain = [Int]
    type Engine TrivialDomain = IO

    top = return [0..10]

    -- ...
    complement a = top >>= (flip exclude) a
    exclude a b  = return $ filter (not . (`elem` b)) a
    -- ...
但是我不断地犯下我不理解的错误

test3.hs:25:21:
    Couldn't match type ‘Engine d0’ with ‘IO’
    The type variable ‘d0’ is ambiguous
    Expected type: IO (Set d0)
      Actual type: Engine d0 (Set d0)
    In the first argument of ‘(>>=)’, namely ‘top’
    In the expression: top >>= (flip exclude) a
test3.hs:25:35:
    Couldn't match type ‘Set d1’ with ‘[Int]’
    The type variable ‘d1’ is ambiguous
    Expected type: Set d0 -> [Int] -> IO [Int]
      Actual type: Set d1 -> Set d1 -> Engine d1 (Set d1)
    In the first argument of ‘flip’, namely ‘exclude’
    In the second argument of ‘(>>=)’, namely ‘(flip exclude) a’

我希望
Engine d(Set d)
在实例声明中解析为
IO[Int]
,但情况似乎并非如此。至少GHC不这么认为。我缺少什么?

在您的例子中,关联的类型不足以推断方法的类型

您拥有类
域d
,并且
集合和
引擎
d
相关联。这意味着,只要在我们的程序中有一个已知的
d
域d
实例,GHC就可以解析
集合d
引擎d
。但这并不能反向工作。GHC无法从
集合d
引擎d
的存在中解析
d
实例,因为完全可能存在具有相同
集合
引擎
类型的不同
实例

由于类方法只提到
Set
Engine
域d
永远不能从方法使用中推断出来

根据你的目标,你可以做一些事情

首先,您可以使
d
依赖于
Set
Engine

class Domain set engine where
  type DomainOf set engine :: *
  -- ...
更一般地说,它使您能够更灵活地强制执行类型之间的依赖关系。例如,您可以明确声明每个
集合
只有一个
d
,这足以恢复良好的类型推断:

class Domain d set engine | d -> set engine, set -> d where

    top        :: engine set
    complement :: set -> engine set
    exclude    :: set -> set -> engine set

data TrivialDomain = TrivialDomain

instance Domain TrivialDomain [Int] IO where

    top = return [0..10]

    complement a = top >>= (flip exclude) a

    exclude a b  = return $ filter (not . (`elem` b)) a
最后,如果要使用原始类,必须向方法中添加
Proxy d
参数,以使实例和关联类型可解析:

import Data.Proxy

data Transition = Transition

class Domain d where
    type Set d
    type Engine d :: * -> *

    top        :: Proxy d -> Engine d (Set d)
    complement :: Proxy d -> Set d -> Engine d (Set d)
    exclude    :: Proxy d -> Set d -> Set d -> Engine d (Set d)

data TrivialDomain = TrivialDomain

instance Domain TrivialDomain where
    type Set TrivialDomain = [Int]
    type Engine TrivialDomain = IO

    top _ = return [0..10]

    complement d a = top d >>= (flip (exclude d)) a
    exclude d a b  = return $ filter (not . (`elem` b)) a
这里,
Proxy d
的目的是精确地指定要使用的实例

然而,这意味着我们必须在每个方法使用上编写
top(Proxy::Proxy d)
(与其他方法类似),这相当繁重。使用GHC 8,我们可以省略代理,而使用:


关于最后一个例子,你能在GHC8的类级别编写
top@d
吗?我想我们不能。默认情况下,
forall
-绑定类型变量可以
@
-应用,但是我们显然不能在
方法类型中为所有d.编写
。我发现,
@
在类方法上运行良好,并且可以按类类型参数的顺序使用。
{-# language TypeApplications, TypeFamilies #-}

-- ...

instance Domain TrivialDomain where
    type Set TrivialDomain = [Int]
    type Engine TrivialDomain = IO

    top = return [0..10]

    complement a = top @TrivialDomain >>= (flip (exclude @TrivialDomain)) a
    exclude a b = return $ filter (not . (`elem` b)) a