2016-03-23 70 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.'。我發現'@'可以在類方法上正常工作,並且可以按類類型參數的順序使用。 –