2012-05-07 14 views
5

Beim Versuch Data.Has zu verwenden, ich habe wie die folgende Schreiben von Code:Haskell Typ Familien Verständnis Fehlermeldung

data Name = Name; type instance TypeOf Name = Text 
type NameRecord = FieldOf Name; 

ich gefunden habe:

instance I NameRecord where 
    ... 

Wirft einen Compiler-Fehler, nämlich:

Illegal type synonym family application in instance

Während:

instance (NameRecord ~ a) => I a where 
    ... 

Kompiliert in Ordnung.

Ich glaube, der Fehler bezieht sich auf this Ticket in GHC, als ungültig markiert.

Die Antwort auf das Ticket sagt:

I am not sure what you are suggesting. We cannot automatically transform

instance C (Fam Int) -- (1) 

into

instance (Fam Int ~ famint) => C famint -- (2) 

This works if there is only one instance, but as soon as there are two such instances, they always overlap.

Maybe you are suggesting that we should do it anyway and programmers should just take the implicit transformation into account. I don't think that this is a good idea. It's confusing for very little benefit (as you can always write the transformed instance yourself with little effort).

Kann jemand auf dieser Erklärung ausarbeiten, vielleicht mit einigen Beispielcode, wo die (1) ausfällt, aber (2) nicht, und warum?

Antwort

4

Ein Fall, in dem (1) versagt, aber (2) nicht trivial ist; weil Art Synonyme (type ExampleOfATypeSynonym = ...) sind nicht im Fall Erklärungen erlaubt, aber sie sind in Zwänge erlaubt, jede Situation, wo man nur ein Beispiel wie folgt aussehen:

:

-- (1) 
class Foo a 
type Bla =() 
instance Foo Bla 

... kann in umgewandelt werden

Der einzige Grund, warum (1) fehlschlägt, ist, weil Typ-Synonyme in Instanz-Deklarationen nicht erlaubt sind, weil Typ-Synonyme wie Typ-Funktionen sind: Sie bieten eine unidirektionale Zuordnung von einem Typnamen zu einem Typnamen. Also, wenn Sie eine type B = A und einehabenist es nicht offensichtlich, dass stattdessen eine Instanz von Foo A erstellt wird. Die Regel existiert, so dass Sie instance Foo A stattdessen schreiben müssen, um zu verdeutlichen, dass dass der Typ ist, der die Instanz tatsächlich erhält.

Die Verwendung von Typfamilien ist in diesem Zusammenhang irrelevant, da das Problem eher darin besteht, dass Sie ein Typensynonym, den Typ NameRecord, verwenden. Sie müssen auch bedenken, dass das Kompilieren fehlschlägt, wenn das Typensynonym direkt entfernt und durch FieldOf Name ersetzt wird; Das liegt daran, dass eine "Typfamilie" nur eine erweiterte Version von Typ-Synonymen ist, so dass FieldOf Name in diesem Zusammenhang auch ein "Typ-Synonym" für Name :> Text ist. Sie müssen stattdessen eine Datenfamilie und eine Dateninstanz verwenden, um eine "bidirektionale" Zuordnung zu erhalten.

Weitere Informationen zu Datenfamilien finden Sie in der GHC documentation.


Ich denke du meinst "... wo (2) scheitert aber (1) nicht ..."

Lasst uns vorstellen, dass wir eine Art Klasse wie so haben:

class Foo a where 
    foo :: a 

Nun Instanzen wie so schreiben können:.

instance Foo Int where 
    foo = 0 

instance Foo Float where 
    foo = 0 

main :: IO() 
main = print (foo :: Float) 

Dies funktioniert wie man es erwarten würde, aber wenn Sie den Code in dieser Transformation:

{-# LANGUAGE FlexibleInstances, TypeFamilies #-} 
class Foo a where 
    foo :: a 

instance (a ~ Int) => Foo a where 
    foo = 0 

instance (a ~ Float) => Foo a where 
    foo = 0 

main :: IO() 
main = print (foo :: Float) 

es lässt sich nicht kompilieren, es zeigt den Fehler:

test.hs:5:10: 
    Duplicate instance declarations: 
     instance a ~ Int => Foo a -- Defined at test.hs:5:10-27 
     instance a ~ Float => Foo a -- Defined at test.hs:8:10-29 

Also, das ist das Beispiel, das Sie hoffentlich gesucht haben. Dies geschieht nur, wenn mehr als eine Instanz von Foo diesen Trick verwendet. Warum das?

Wenn GHC Typklassen auflöst, wird nur der Deklarationskopf der Instanz betrachtet. d.h. es ignoriert alles vor dem =>. Wenn es eine Instanz gewählt hat, "schreibt" es sich darauf ein und prüft die Bedingungen vor dem =>, um zu sehen, ob sie wahr sind. Also erst mal sieht es zwei Instanzen:

instance Foo a where ... 
instance Foo a where ... 

Es ist offensichtlich unmöglich zu entscheiden, welcher auf diesen Informationen allein basieren.

+0

Interessanterweise wissen Sie zufällig, ob die Methode von GHC zum Auflösen von Typklassen eine explizite Designentscheidung ist? Und wenn ja, der Grund dafür? (Oder ist die Alternative einfach zu kompliziert, um sie sauber zu implementieren?) – huon

+3

Der Grund dafür ist, dass der Haskell-Bericht verlangt, dass er sich so verhält. Der Grund für ** das ** ist, dass wenn es sich nicht so verhalten würde, es einen Algorithmus geben müsste, der eine Heuristik für "wie gut ein Typ zu einer Beschränkung passt" gibt; Sie müssten argumentieren "Ja, dieser Typ passt zu dieser Klasseninstanz, aber diese andere Instanz passt viel besser, weil {weniger Einschränkungen, kürzere Reduktionsdistanz, ...}." Es könnte möglich sein, eine solche Heuristik zu entwickeln, aber es würde die Open-World-Annahme sprengen, die ein Schlüsselkonzept ist, wenn es um Typklassen geht. – dflemstr

+0

Stellen Sie sich 'instance String ~ a => Foo a' und' instance a ~ [b] => Foo a' vor. Das ist ein Beispiel für Instanzen, in denen Sie einen Algorithmus benötigen, um 'Foo [Char]' aufzulösen. – dflemstr