2014-11-12 4 views
11

Ich verstehe, dass, wennWarum wird bei der Auswahl der Typklasseninstanz in Haskell der Kontext nicht berücksichtigt?

instance (Foo a) => Bar a 
instance (Xyy a) => Bar a 

mit GHC nicht die Zusammenhänge betrachten, und die Instanzen werden als Duplikat gemeldet.

Was ist kontraintuitiv, das (ich denke) nach der Auswahl einer Instanz, es muss immer noch überprüfen, ob der Kontext übereinstimmt, und wenn nicht, die Instanz verwerfen. Warum also nicht die Reihenfolge umkehren und Instanzen mit nicht übereinstimmenden Kontexten verwerfen und mit der verbleibenden Menge fortfahren?

Wäre das in irgendeiner Weise unlösbar? Ich sehe, wie es im Voraus mehr Einschränkung Auflösung Arbeit verursachen könnte, aber wie es UndecidableInstances/IncoherentInstances ist, könnte nicht ein ConsiderInstanceContexts sein, wenn "Ich weiß, was ich tue"?

+1

Welche Instanz sollte GHC wählen, ob 'a' ein 'foo' ist und ein' Xyy'? – mb14

+1

@ mb14: Eine beliebige. ('InkoherentInstances' macht schon etwas ähnliches, und damit kann ich leben). – ron

+0

Tatsächlich scheint der einzige Unterschied darin zu bestehen, dass 'InkohärenteInstanzen' es GHC erlauben, sich an eine der beiden Instanzen zu binden, möglicherweise einen mit einem erfüllbaren Kontext zu verwerfen und sich an den unerfüllbaren zu binden (was einen Fehler auslösen wird). Diese Frage fragt stattdessen, warum GHC keine "BacktrackOnContextFailures" -Flagge hat, wenn ich richtig verstehe, so dass die richtige Instanz schließlich versucht wird. Es wird im schlimmsten Fall sicherlich zur Unnachgiebigkeit führen, aber wir haben bereits "UnentscheidbareInstanzen", die die Leistung erheblich beeinträchtigen können. – chi

Antwort

3

Dies beantwortet nicht die Frage, warum dies der Fall ist. Beachten Sie jedoch, dass Sie immer einen newtype Wrapper definieren zwischen den beiden Fällen eindeutig zu machen:

newtype FooWrapper a = FooWrapper a 
newtype XyyWrapper a = XyyWrapper a 

instance (Foo a) => Bar (FooWrapper a) 
instance (Xyy a) => Bar (XyyWrapper a) 

Dies hat den zusätzlichen Vorteil, dass entweder um einen FooWrapper indem oder ein XyyWrapper Sie explizit, welches der beiden Instanzen steuern Sie Ich würde gerne verwenden, wenn Sie beide zufriedenstellen.

+0

Sicher. Mein Anwendungsfall wäre eher "Wählen Sie eine beste Instanz", ohne dass der Client etwas umhüllen/wissen muss. Zum Beispiel mit einer 'LinearSearch'-, 'BinarySearch'- und' Search'-Klasse, wobei 'Search' standardmäßig auf die binäre Suche, wenn die Instanz existiert, ansonsten lineare Suche (falls vorhanden). (Nun, willkürliche Wahl wäre hier nicht gut genug, eher wie eine geordnete Wahl, aber lass es beiseite). – ron

+0

Zum Spaß, betrachten wir nicht die Alternative, explizite Instanzen von 'Search' für jeden Datentyp zu machen. – ron

+4

Ich sehe, dass dies ein Gedankenexperiment und eine interessante Frage ist (die ich nicht beantworten kann). Wie auch immer, FWIW, ich würde es als einen schlechten Entwurf für den Compiler betrachten, willkürliche oder sogar nicht-deterministische Entscheidungen zu treffen, die das Verhalten Ihres Codes völlig verändern. – DanielM

2

Klassen sind ein bisschen komisch. Die ursprüngliche Idee (die immer noch ziemlich funktioniert) ist eine Art von syntaktischem Zucker um was sonst wäre data Aussagen. Zum Beispiel können Sie sich vorstellen:

data Num a = Num {plus :: a -> a -> a, ... , fromInt :: Integer -> a} 
numInteger :: Num Integer 
numInteger = Num (+) ... id 

dann können Sie Funktionen schreiben, die z. Typ:

Also die Idee ist "wir werden automatisch alle diese Bibliothek Code ableiten." Die Frage ist, wie finden wir die Bibliothek, die wir wollen? Es ist einfach, wenn wir eine Bibliothek vom Typ Num Int haben, aber wie tun erweitern wir es auf „eingeschränkte Instanzen“, basierend auf Funktionen vom Typ:

fooLib :: Foo x -> Bar x 
xyyLib :: Xyy x -> Bar x 

Die vorliegende Lösung in Haskell ist ein tun Art-Muster- passe die Ausgabetypen dieser Funktionen an und übertrage die Eingaben auf die resultierende Deklaration. Aber wenn es gibt zwei Ausgänge des gleichen Typs, würden wir eine combinator brauchen die diese in geht:

eitherLib :: Either (Foo x) (Xyy x) -> Bar x 

und im Grunde das Problem ist, dass es kein guter Constraint-combinator dieser jetzt Art. Das ist dein Einwand.

Nun, das stimmt, aber es gibt Wege, etwas in der Praxis moralisch ähnlich zu erreichen. Angenommen, definieren wir einige Funktionen mit Typen:

data F 
data X 
foobar'lib :: Foo x -> Bar' x F 
xyybar'lib :: Xyy x -> Bar' x X 
bar'barlib :: Bar' x y -> Bar x 

Klar, dass die y eine Art „Phantom-Typ“ ist mit Gewinde durch all dies, aber es bleibt mächtig, weil da wir ein Bar x wollen wir die Notwendigkeit für eine propagieren Bar' x y und gegeben die Notwendigkeit für die Bar' x y werden wir entweder Bar' x X oder Bar' x y generieren. Mit Phantom-Typen und Multi-Parameter-Typ-Klassen erhalten wir das gewünschte Ergebnis.

Weitere Informationen: https://www.haskell.org/haskellwiki/GHC/AdvancedOverlap

Verwandte Themen