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