2016-03-23 31 views
3

我试图拼凑下面的类Domain及其实例TrivialDomain型歧义

{-# 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不这么认为。我错过了什么?

回答

6

在你的情况下,关联类型不足以推断方法的类型。

您有类Domain dSetEngined关联。这意味着只要我们的程序中有已知的d已知的Domain d实例,GHC就可以解析Set dEngine d。但是这不会倒退。 GHC无法从存在Set dEngine d的情况下解析dDomain实例,因为完全有可能存在具有相同SetEngine类型的不同Domain实例。

由于您的课程方法只提及SetEngine,Domain d永远不能从方法使用推断。

根据你的目标,你可以做几件事情。

首先,你可以做d取决于SetEngine

class Domain set engine where 
    type DomainOf set engine :: * 
    -- ... 

更一般地,FunctionalDependencies为您提供了更大的灵活性,以强制类型之间的依赖关系。例如,您可以特别声明只有一个d每个Set,这足以恢复良好的类型推断:

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我们可以省略Proxy S和使用TypeApplications代替:

{-# 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 
+0

关于最后一个例子,你能不能写在GHC8类级别'顶部@ D'? – jakubdaniel

+0

我认为我们不能。默认情况下,'forall'绑定的类型变量可以是'@'应用的,但是显然,我们不能在'Domain'方法类型中写入'forall d.'。我发现'@'可以在类方法上正常工作,并且可以按类类型参数的顺序使用。 –