2017-12-10 2 views
8

Da einige Typdefinitionen:Warum überprüft diese Haskell-Code-Typ mit fundeps aber einen unantastbaren Fehler mit Typfamilien?

data A 
data B (f :: * -> *) 
data X (k :: *) 

... und das typeclass:

class C k a | k -> a 

... diese (sehr gekünstelt im Sinne eines minimalen Beispiel) Funktionsdefinitionen typecheck:

f :: forall f. (forall k. (C k (B f)) => f k) -> A 
f _ = undefined 

g :: (forall k. (C k (B X)) => X k) -> A 
g = f 

Wenn wir jedoch eine Typfamilie anstelle einer Klasse mit einer funktionalen Abhängigkeit verwenden:

type family F (k :: *) 

... dann die entsprechenden Funktionsdefinitionen nicht typecheck:

f :: forall f. (forall k. (F k ~ B f) => f k) -> A 
f _ = undefined 

g :: (forall k. (F k ~ B X) => X k) -> A 
g = f 

• Couldn't match type ‘f0’ with ‘X’ 
    ‘f0’ is untouchable 
     inside the constraints: F k ~ B f0 
     bound by a type expected by the context: 
       F k ~ B f0 => f0 k 
    Expected type: f0 k 
    Actual type: X k 
• In the expression: f 
    In an equation for ‘g’: g = f 

I Abschnitt 5.2 der the OutsideIn(X) paper lesen, die berührbaren und unberührbaren Typ Variablen, und ich Art beschreibt verstehe, was hier vor sich geht. Wenn ich ein zusätzliches Argument f hinzufügen, die die Wahl der f außerhalb des inneren forall drückt, dann geht das Programm typechecks:

f :: forall f a. f a -> (forall k. (F k ~ B f) => f k) -> A 
f _ _ = undefined 

g :: forall a. X a -> (forall k. (F k ~ B X) => X k) -> A 
g = f 

Doch was speziell hat mich so verwirrt, in diesem Beispiel ist, warum die funktionale Abhängigkeit verschiedener hat Verhalten. Ich habe gehört, dass Leute zu verschiedenen Zeiten behaupten, funktionale Abhängigkeiten wie diese seien äquivalent zu einer Typfamilie plus einer Gleichheit, aber dies zeigt, dass das nicht wirklich wahr ist.

Welche Informationen liefert die funktionale Abhängigkeit in diesem Fall, die f in einer Weise instanziiert werden kann, die die Typenfamilie nicht enthält?

+0

Beachten Sie, dass 'g = f @ X 'auch Typ überprüft. Es scheint, dass der Inferenzalgorithmus sich nicht verpflichtet, die Typvariable "f" als "X" zu wählen. Ich kann nicht sehen, warum - normalerweise, weil es einen anderen Wert von "f" geben könnte, der den Typ "(für k (Fk ~ B f) => fk) -> A" gleich "(forall k . (Fk ~ BX) => Xk) -> A'. Hier scheint 'f ~ X' die einzige Lösung für mich zu sein (oder?). Interessant. – chi

+0

@chi Ich denke schon, aber ich weiß nicht genug über diesen speziellen Fall des Typcheckers, um sicher einen Fehler zu öffnen. Vielleicht sollte ich sowieso ein Ticket öffnen, und wenn es Verhalten ist, werde ich zumindest wahrscheinlich wissen warum? –

+0

Interessant in der Tat! Ich habe jetzt zweimal meine Meinung darüber geäußert, ob das eigentlich eine Überprüfung mit _neither_ fundeps nicht geben soll, Familien oder nur mit fundeps oder mit beiden. Ich verstehe einfach nicht gut genug, wie die Einschränkungen gelöst sind, um zu entscheiden.Aber ich halte es zumindest nicht für plausibel, dass nur die fundep-Version funktionieren sollte: Der entscheidende Unterschied scheint zu sein, dass Typklassen mit ihren Superklassen "entwirrt" werden können (das 'f' wird aus' Bf' extrahiert), aber aus einer Gleichheitsbedingung ist dies nicht möglich. – leftaroundabout

Antwort

0

Ich weiß nicht, ob ich dies als eine Antwort schreiben soll, weil es immer noch ziemlich hand wavey ist, aber ich denke, das ist, was im Wesentlichen auf ist ist los:

einen (C k (B X)) => X k Wert zu bewerten, müssen Sie entscheiden, ein konkreter Typ für k, und zeigen Sie auf die instance C k (B X), die die Einschränkungen erfüllt. Um dies zu tun, müssen Sie ausdrücken, dass die Klasse 'a Argument die Form B f hat, aus dem der Compiler den f Typ extrahieren kann (und herausfinden, dass es in diesem Fall X ist) – wichtig ist, kann es vor tatsächlich suchen an der Instanz, die der Punkt wäre, an dem f unantastbar werden würde.

Um eine (F k ~ B X) => X k auszuwerten, ist es ein bisschen anders. Hier müssen Sie nicht auf eine konkrete Instanz verweisen, Sie müssen lediglich garantieren, dass , wenn der Compiler die typefamily für F k nachgeschlagen hat, dann wäre dieser Typ der gleiche Typ wie B X. Bevor jedoch die Instanz tatsächlich nachgeschlagen wird, kann der Compiler hier nicht folgern, dass F k die Form B f hat, und daher auch nicht verwenden, um f mit dem äußeren Quantifizierungsargument wegen Unberührbarkeit zu vereinheitlichen.

Daher ist GHC Verhalten zumindest nicht völlig unangemessen. Ich bin mir immer noch nicht sicher, ob es sich so verhalten sollte.

+0

Während das, was Sie sagen, einen Sinn ergibt, bin ich ein bisschen skeptisch gegenüber dieser Antwort. Hier ist der Grund: Wenn Sie 'data B' in' type family B' ändern, wird die Version mit der funktionalen Abhängigkeit immer noch threechecks! Es scheint, als würde es Ihrer Erklärung dafür widersprechen, warum die Fundep-Version so funktioniert, wie sie es tut, da "B f ~ B X" nicht "f ~ X" bedeutet, wenn "B" eine Typfamilie ist. –

0

OK Ich hatte eine Chance, damit zu spielen. Es gibt mehrere Ablenkungen:

In der Type Family-Version gibt nur die Definition für f Fehler 'f0' is untouchable. (Sie können das mit AllowAmbiguousTypes unterdrücken; das verschiebt den Fehler einfach auf g). Lassen Sie uns g hier auf ignorieren.

Dann ohne AllowAmbiguousTypes die Fehlermeldung für f gibt weitere Informationen:

• Couldn't match type ‘f0’ with ‘f’ 
    ‘f0’ is untouchable 
     inside the constraints: F k ~ B f0 
     bound by the type signature for: 
       f :: F k ~ B f0 => f0 k 
    ‘f’ is a rigid type variable bound by 
    the type signature for: 
     f :: forall (f :: * -> *). (forall k. F k ~ B f => f k) -> A 
    Expected type: f0 k 
    Actual type: f k 

Aha! ein rigid type variable Problem. Ich denke, weil f durch die Gleichheit Einschränkung von k festgelegt ist, die ebenfalls starr ist, weil ...

zum FunDep Version Drehen ohne g, auf welche Arten können wir f nennen? Versuchen

f (undefined undefined :: X a)   -- OK 
f (undefined "string" :: X String)  -- rejected 
f Nothing        -- OK 
f (Just 'c')        -- rejected 

die Ablehnungsnachricht (für das X String Beispiel)

• Couldn't match type ‘k’ with ‘String’ 
    ‘k’ is a rigid type variable bound by 
    a type expected by the context: 
     forall k. C k (B X) => X k 
    Expected type: X k 
    Actual type: X String 
• In the first argument of ‘f’, namely 
    ‘(undefined "string" :: X String)’ 

Beachten Sie die Nachricht über k ist, nichtf, die aus dem FunDep bestimmt wird - oder wäre, wenn wir könnten finden Sie eine geeignete k.

Erklärung

Die Signatur für die Funktion f sagt k existenzquantifizierte ist/höherrangigen. Dann können wir keine Typinformationen über k in den umgebenden Kontext entkommen lassen. Wir können keinen (nicht unteren) Wert für k angeben, da sein Typ in die forall eindringen würde.

Hier ist ein einfacheres Beispiel:

f2 :: forall f. (forall k. f k) -> A 
f2 _ = undefined 

f2 Nothing         -- OK 
f2 (Just 'c')        -- rejected rigid type var 

so dass die ursprüngliche FunDep Version kompiliert ist eine Ablenkung: Es ist nicht bewohnt werden kann. (Und das ist ein häufiges Symptom mit FunDep s, nach meinem früheren Verdacht.)

+0

BTW erhalten Sie eine ähnliche 'f0 ist unberührbar' Ablehnung mit dieser Signatur' f :: forall f. (für alle k. f ~ X => f k) -> A '. No Type Family oder FunDep in Sichtweite. – AntC

Verwandte Themen