2016-03-23 15 views
3

Ich versuche, die folgende Klasse zusammen Domain und seine Instanz TrivialDomainTyp Mehrdeutigkeit in Familien Haskell Typ

{-# 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 
    -- ... 

aber ich halte die folgende Fehlermeldung erhalten, die ich nicht

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’ 

zu verstehen, würde ich erwartet Engine d (Set d) zu IO [Int] in der Instanzdeklaration aufzulösen, was nicht der Fall zu sein scheint. Zumindest glaubt GHC das nicht. Was vermisse ich?

Antwort

6

In Ihrem Fall reichen die zugehörigen Typen nicht aus, um auf die Methodenarten zu schließen.

Sie haben die Klassen Domain d und Set und Engine sind mit d verknüpft. Dies bedeutet, dass GHC bei jeder bekannten d in unserem Programm mit einer bekannten Domain d Instanz Set d und Engine d auflösen kann. Aber das funktioniert nicht rückwärts. GHC kann keine d oder eine Domain Instanz von dem Vorhandensein eines Set d oder Engine d auflösen, da es durchaus möglich ist, dass es verschiedene Domain Instanzen mit denselben Set und Engine Typen gibt.

Da Ihre Klassenmethoden nur Set und Engine angeben, kann Domain d niemals aus der Methodenverwendung abgeleitet werden.

Sie könnten je nach Ihren Zielen ein paar Dinge tun.

Erstens könnten Sie d abhängig machen auf Set und Engine:

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

Allgemeiner FunctionalDependencies Sie viel mehr Flexibilität gibt Abhängigkeiten zwischen verschiedenen Arten zu erzwingen. Zum Beispiel können Sie ausdrücklich erklären, dass es nur eine d für jede Set ist, was eine gute Typinferenz zu erholen genug ist:

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 

Schließlich, wenn Sie Ihre ursprüngliche Klasse verwenden möchten, müssen Sie Proxy d Parameter hinzufügen auf Ihre Methoden, die Instanz und die zugehörigen Typen auflösbar zu machen, um: ist

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 

Hier ist der Zweck der Proxy d genau angeben, welche Instanz Sie verwenden möchten.

Dies bedeutet jedoch, dass wir top (Proxy :: Proxy d) auf jede Methodenverwendung schreiben müssen (ähnlich wie bei anderen Methoden), was ziemlich beschwerlich ist. Mit 8 GHC können wir Proxy s weglassen und TypeApplications statt:

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

in Bezug auf das letzte Beispiel könnten Sie 'top @ d' auf Klassenebene in GHC8 schreiben? – jakubdaniel

+0

Ich denke, wir können nicht. Standardmäßig können 'forall'-gebundene Typ-Variablen' @ '-appliziert werden, aber wir können natürlich nicht' forall d.' in den 'Domain'-Methodentypen schreiben. Ich habe festgestellt, dass '@' auf Klassenmethoden gut funktioniert und in der Reihenfolge der Klassentypparameter verwendet werden kann. –