2011-12-17 13 views
20

Próbuję przejrzeć przykład YesNo z książki Learn You a Haskell for Great Good!.Niejednoznaczna zmienna typu "a0" w ograniczeniach

Oto mój kod źródłowy:

module Main where 

main :: IO() 

main = putStrLn (show (yesno 12)) 

class YesNo a where 
    yesno :: a -> Bool 


instance YesNo Bool where 
    yesno b = b 

instance YesNo [a] where 
    yesno [] = False 
    yesno _ = True 


instance YesNo Int where 
    yesno 0 = False 
    yesno _ = True 

Kiedy wykonać ten kod pojawia się następujący wyjątek:

Ambiguous type variable `a0' in the constraints: 
    (YesNo a0) arising from a use of `yesno' 
      at /Users/mkhadikov/Projects/personal/haskell/hello-world/yesno.hs:5:25-29 
    (Num a0) arising from the literal `12' 
      at /Users/mkhadikov/Projects/personal/haskell/hello-world/yesno.hs:5:31-32 
Probable fix: add a type signature that fixes these type variable(s) 
In the first argument of `show', namely `(yesno 12)' 
In the first argument of `putStrLn', namely `(show (yesno 12))' 
In the expression: putStrLn (show (yesno 12)) 

Czy możesz wyjaśnić, co jest nie tak z tym kodem?

Odpowiedz

31

Problem polega na tym, że nie wie, jaki jest typ 12! Może to być dowolnytyp z instancją Num:

musisz określić typ chcesz bezpośrednio: spróbuj putStrLn (show (yesno (12 :: Int))).

Dlaczego GHC nie może wybrać Int, ponieważ żaden inny wybór nie zadziała, pytasz? Dobre pytanie. Odpowiedź jest taka, że ​​w przypadku systemu Haskell, dodawanie instancji nie może unieważnić istniejących poprawnych programów ani zmienić ich zachowania. (Jest to określane jako otwarte założenie świata.) Jeśli wybrał Int, to co by się stało, gdyby dodać instance YesNo Integer? Wybór stanie się niejednoznaczny, a twój program się zepsuje!

Kiedy więc chcesz użyć takiej typograficznej z poliforyczną wartością, musisz określić, jaki typ masz na myśli. W praktyce nie powinno to wyglądać zbyt często, ponieważ zazwyczaj będzie istnieć jakiś otaczający kontekst, aby zmusić typ do tego, co chcesz; to dotyczy głównie literałów numerycznych.

5

Problem polega na tym, że 12 rzeczywiście ma typ Num a => a, a nie Int zgodnie z oczekiwaniami. Jeśli dodasz jawną anotację typu, taką jak 12 :: Int, powinna ona zostać skompilowana.

+0

Czy to możliwe, aby stworzyć swój własny rodzaj konstruktorów danych, które działają podobnie jak '12' w Haskell? Wydaje się, że gdy tworzysz konstruktor danych, konstruuje on wartość jednego typu. Ale kiedy piszesz '12', jak widzisz, nie tworzy wartości jednego typu, ale wartość dowolnego typu, gdzie typ jest ograniczony przez Num. Tak więc "Num a => a". Czy interpretuję to poprawnie? Czy jest to podobne do konstruowania wartości, która ma egzystencjalne/związek? – CMCDragonkai

+0

@CMCDragonkai Jestem trochę rozmyta na szczegóły, ale zasadniczo typeclass 'Num' zawiera funkcję' fromInteger :: Num a => Integer -> a'. Literał numeryczny działa tak, jakby został wywołany 'fromInteger'. – fuz

+0

Ach, więc "a" w pewnym sensie staje się egzystencjalnym/związkowym. Bardzo interesujące. – CMCDragonkai

1

Miałem ten sam problem.

Jest to rozwiązanie, może nie najlepszy, ale to działa:

class YesNo a where 
    yesno :: a -> Bool 


instance YesNo Int where 
    yesno 0 = False 
    yesno _ = True 


instance YesNo Integer where 
    yesno 0 = False 
    yesno _ = True 
Powiązane problemy