2013-04-17 23 views
12

wiem, że to kod jest nieco głupie, ale może ktoś wyjaśnić dlaczego tak isList [42] powraca True podczas gdy isList2 [42] drukuje False i jak temu zapobiec? Chciałbym lepiej zrozumieć niektóre z bardziej niejasnych rozszerzeń typu GHC i pomyślałem, że byłby to interesujący przykład, aby to zrozumieć.Haskell Nakładające/niespójny Przypadki

{-# 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

Ponieważ program jest niespójny. Naprawdę, czego oczekiwałeś *? –

+1

Oczekiwalem, że wyjście będzie "True True", a nie "True False". Dlaczego oczekujesz, że wynik będzie "True False"? Dlaczego niespójny program nie powróci do "Prawdziwej Prawdy"? Czy mówisz, że nie ma powodu dla wyjścia, a wynik jest nieokreślony i czasami może on zwracać "True True"? – Clinton

+1

prawdopodobny duplikat [Jak działa IncoherentInstances?] (Http://stackoverflow.com/questions/8371499/how-does-incoinneinstrukcje-work) –

Odpowiedz

15

To naprawdę bardzo proste. Zapytajmy GHCi jaki rodzaj isList2 jest:

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

To nie pasuje do instancji [a] (choć mógł, poprzez zjednoczenie), ale nie od razu dopasować instancji a. Dlatego GHC wybiera instancję a, więc isList2 zwraca False.

To zachowanie jest dokładnie tym, co oznacza IncoherentInstances. Właściwie jest to raczej niezła demonstracja tego.


Hilariously, jeśli po prostu wyłączyć IncoherentInstances otrzymujemy dokładnie odwrotny skutek, a GHCi teraz mówi tak:

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

Dzieje się tak dlatego isList2 jest wiążącym najwyższego poziomu nie określono przy użyciu składni funkcji , a tym samym podlega ograniczeniu Mormorphism. Więc specjalizuje się w instancji, z którą jest faktycznie używana.

Dodawanie NoMonomorphismRestriction jak również wyłączenie IncoherentInstances, otrzymujemy zamiast tego:

∀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' 

Jaki jest spodziewany nakładających zachowanie, za przykład wybranego na podstawie użytkowania i reklamacji, jeśli wybór jest niejednoznaczny.


Jeśli chodzi o edycję tego pytania, nie wierzę, że pożądany wynik jest możliwy bez adnotacji typu.

Pierwszą opcją jest nadanie isList2 podpisu typu, który uniemożliwia IncoherentInstances wybieranie instancji zbyt wcześnie.

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

Prawdopodobnie będziesz musiał zrobić to samo nigdzie indziej isList jest wymienione (nawet pośrednio) nie są stosowane do kłótni.

Drugą opcją jest ujednoznacznienie literałów numerycznych i wyłączenie IncoherentInstances.

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

W tym przypadku, nie ma wystarczająco dużo informacji, aby wybrać najbardziej konkretny przypadek, więc OverlappingInstances robi jego rzecz.

+0

Edytowałem pytanie. Czy mógłbyś odpowiedzieć na pytanie, w jaki sposób mogę skompilować powyższe i zwrócić 'False False True True'? – Clinton

+0

@ Clinton: Nie ma * miłego * sposobu, aby to zrobić, nie sądzę. Zobacz edycję do mojej odpowiedzi. –

+0

@Clinton Pojadę trochę dalej i powiem, że NIE MOŻNA to być przyjemny sposób. GHC musi przestrzegać założeń otwartego świata, jeśli chodzi o typologie i nigdy nie może podejmować decyzji w oparciu o brak pewnej typografii. Aby 'isList 42' zwrócił' Fałsz', faktycznie POTRZEBUJESZ naruszyć to założenie. Ponieważ musisz założyć, że nie istnieje "instancja Num [a], gdzie ..." ponieważ istnienie takiej czcionki typowej zmieni odpowiedź z "Fałsz" na "to zależy od tego, którą instancję wybierzesz", która nie jest poprawna wartość typu 'Bool'. – semicolon

2

Poniższy code załatwia sprawę bez konieczności IncoherentInstances:

{-# 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]) 

bym nie zalecamy stosowanie IncoherentInstances, wydaje się powodować wiele kłopotów, jak można dyskretnie zadzwonić różnych przeciążeń w zależności od kontekstu dość łatwo .