2014-07-15 12 views
6

Obecnie uczę się Haskell i próbuję zrozumieć, jak ocenia się typki i jak działają let i where. Ten kod działa poprawnie:Ocena, niech i gdzie w Haskell

{-# LANGUAGE FlexibleInstances #-} 
class Expr a where 
    literal :: Integer -> a 

instance Expr Integer where 
    literal = id 

instance Expr [Integer] where 
    literal i = [i] 

coerceInteger :: Integer -> Integer 
coerceInteger = id 

main = print $ coerceInteger (literal 100) : literal 100 -- Prints [100,100] 

ale zmieniając główną funkcję do

main = print $ coerceInteger expr : expr 
    where expr = literal 200 

powoduje błąd kompilatora:

Couldn't match expected type `[Integer]' with actual type `Integer' 
In the second argument of `(:)', namely `expr' 
In the second argument of `($)', namely `coerceInteger expr : expr' 
In the expression: print $ coerceInteger expr : expr 

Zgaduję, to dlatego, że w pierwszym main metodzie literal 100 jest oceniany dwa razy, podczas gdy w drugim przykładzie literal 200 jest oceniany tylko raz, więc kompilator jest zmuszony wybrać typ.

Jak mogę uwzględnić ten kod, aby uniknąć powtórzenia się, nie powodując tego błędu? Próbowałem używać let expr = literal 300 in ..., ale wpadłem na ten sam problem.

Odpowiedz

9

Problem polega na tym, że literal 200 jest interpretowany inaczej w dwóch różnych kontekstach za pomocą pierwszego przykładu. Potraktujcie to jako

((:) :: a -> [a] -> [a]) 
    ((coerceInteger :: Integer -> Integer) (literal 100 :: Expr a => a)) 
    (literal 100 :: Expr a => a) 

Wystarczy oparty off typów, kompilator określa, że ​​pierwszy literal 100 musi mieć typ Integer bo to były przekazywane do coerceInteger, ponieważ musi przyjmować wartość typu Integer. Ustawia to również typ (:), aby być teraz Integer -> [Integer] -> [Integer], co oznacza, że ​​ostatni literal 100 musi mieć typ [Integer].

W drugim przykładzie mówimy, że obie mają tę samą wartość, a więc tego samego typu, co jest niemożliwe, ponieważ druga musi być listą dla sprawdzania typu dla (:).

Dzieje się tak z powodu przerażającego ograniczenia monomorfizmu. Można rozwiązać ten problem na dwa sposoby: jeden, wyłącz ograniczenie monomorfizm z {-# LANGUAGE NoMonomorphismRestriction #-}, lub można zapewnić wyraźny typ do expr że utrzymuje ją uogólnić:

main :: IO() 
main = print $ coerceInteger expr : expr 
    where 
     expr :: Expr a => a 
     expr = literal 100 

Każda z tych metod pracy, a cokolwiek zdecydować aby to zrobić, polecam zawsze dostarczanie sygnatur typu, aby uniknąć tych problemów.


W rzeczywistości, po dodaniu podpisu typu można nawet robić takie rzeczy jak

main :: IO() 
main = print $ coerceInteger expr : expr : expr : expr : expr : expr 
    where 
     expr :: Expr a => a 
     expr = literal 100 

bez żadnych problemów, to wydrukować [100, 100, 100, 100, 100, 100]. Początkowy coerceInteger jest jednak potrzebny, ponieważ w przeciwnym razie kompilator nie będzie wiedział, jak go utworzyć i dlatego nie będzie miał instancji Show dla print.