2013-04-17 8 views
12

Ich weiß, das ist Code ist ein bisschen albern, aber kann jemand erklären, warum diese isList [42]True zurückgibt, während isList2 [42] Drucke False, und wie dies zu verhindern? Ich würde gerne einige der obskureren GHC-Typ-Erweiterungen besser verstehen, und ich dachte, dies wäre ein interessantes Beispiel, um es herauszufinden.Haskell Overlapping/Inkohärente Instanzen

{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE OverlappingInstances #-} 
{-# LANGUAGE IncoherentInstances #-} 

class IsList a where 
    isList :: a -> Bool 

instance IsList a where 
    isList x = False 

instance IsList [a] where 
    isList x = True 

isList2 = isList 

main = 
    print (isList 42) >> 
    print (isList2 42) >> 
    print (isList [42]) >> 
    print (isList2 [42]) 
+5

Da Ihr Programm ist inkohärent. Wirklich, was hast du * erwartet *? –

+1

Ich erwartete, dass die Ausgabe "True True" ist, nicht "True False". Warum erwarten Sie, dass die Ausgabe "True False" ist? Warum sollte das inkohärente Programm nicht 'True True' zurückgeben? Willst du damit sagen, dass es keinen Grund für die Ausgabe gibt, und das Ergebnis ist undefiniert, und es kann manchmal "True True" zurückgeben? – Clinton

+1

möglich Duplikat von [Wie funktioniert IncoherentInstances arbeiten?] (Http://stackoverflow.com/questions/8371499/how-does-incoherentinstances-work) –

Antwort

15

Es ist wirklich ziemlich einfach. Lassen Sie uns GHCi fragen, was die Art der isList2 ist:

∀x. x ⊢ :t isList2 
isList2 :: a -> Bool 

Dies ist nicht die [a] Instanz überein (obwohl es könnte, über Vereinigung), aber es passen die a Instanz sofort. Daher wählt die GHC a Beispiel isList2 so kehrt False.

Dieses Verhalten ist genau das, was IncoherentInstances Mittel. Eigentlich ist das eine schöne Demonstration davon.


Vergnügt, wenn Sie einfach IncoherentInstances deaktivieren, erhalten wir genau die entgegengesetzte Wirkung, und GHCi sagt jetzt das:

∀x. x ⊢ :t isList2 
isList2 :: [Integer] -> Bool 

Dies geschieht, weil isList2 ist ein Top-Level-Bindung nicht mit der Funktion Syntax definiert und somit der gefürchteten Monomorphie Restriktion unterliegen. Es wird also auf die Instanz spezialisiert, mit der es tatsächlich verwendet wird.

Hinzufügen NoMonomorphismRestriction sowie das Deaktivieren IncoherentInstances, bekommen wir diese statt:

∀x. x ⊢ :t isList2 
isList2 :: IsList a => a -> Bool 
∀x. x ⊢ isList2 'a' 
False 
∀x. x ⊢ isList2 "a" 
True 
∀x. x ⊢ isList2 undefined 

<interactive>:19:1: 
    Overlapping instances for IsList a0 arising from a use of `isList2' 

, das das erwartete überlappende Verhalten ist, mit der Instanz gewählt basierend auf der Verwendung und Beschwerden, wenn die Wahl nicht eindeutig ist.

In Bezug auf die Bearbeitung der Frage glaube ich nicht, dass das gewünschte Ergebnis ohne Typ Anmerkungen möglich ist.

Die erste Möglichkeit besteht darin, isList2 eine Typ-Signatur zu geben, die verhindert, dass IncoherentInstances eine Instanz zu früh auswählt.

isList2 :: (IsList a) => a -> Bool 
isList2 = isList 

Sie werden wahrscheinlich das gleiche tun müssen anderswo isList erwähnt wird (wenn auch indirekt) ohne auf ein Argument angewendet wird.

Die zweite Option ist die Zahlenliterale und deaktivieren IncoherentInstances eindeutig zu machen.

main = 
    print (isList (42 :: Integer)) >> 
    print (isList2 (42 :: Integer)) >> 
    print (isList [42]) >> 
    print (isList2 [42]) 

In diesem Fall gibt es genug Informationen, eine sehr spezifische Instanz zu holen, so OverlappingInstances seine Sache tut.

+0

Ich habe die Frage bearbeitet. Könnten Sie ansprechen, wie ich das oben genannte zum Kompilieren bekommen und 'False False True True' zurückgeben könnte? – Clinton

+0

@ Clinton: Es gibt keine * nette * Möglichkeit, das zu tun, denke ich nicht. Siehe die Bearbeitung meiner Antwort. –

+0

@ Clinton Ich werde ein bisschen weiter gehen und sagen, dass es keine schöne Möglichkeit ist, das zu tun. GHC muss der Open-World-Annahme in Bezug auf Typklassen folgen und darf niemals eine Entscheidung aufgrund des Fehlens einer bestimmten Typklasse treffen. Damit 'isList 42'' False' zurückgibt, müssen Sie diese Annahme tatsächlich verletzen. Wie Sie es annehmen müssen nicht vorhanden 'Instanz Num [a], wo ...', weil die Existenz eines solchen würde typeclass die Antwort von 'false' ändern, um„auf die Instanz ab, die Sie wählen“, was nicht a Gültiger Wert vom Typ 'Bool'. – semicolon

2

Die folgende code funktioniert der Trick ohne IncoherentInstances erfordert:

{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE OverlappingInstances #-} 

class IsList a where 
    isList :: a -> Bool 

instance IsList a where 
    isList x = False 

instance IsList [a] where 
    isList x = True 

isList2 :: (IsList a) => a -> Bool 
isList2 = isList 

main = do 
    print (isList (42 :: Int)) 
    print (isList [42 :: Int]) 
    print (isList2 (42 :: Int)) 
    print (isList2 [42 :: Int]) 

ich nicht IncoherentInstances verwenden würde empfehlen, es scheint eine Menge Ärger zu verursachen, wie Sie leise verschiedene Überlastungen je nach Kontext leicht ganz anrufen .