Dando firma di tipo esplicito, si impedisce GHC dal fare alcune ipotesi circa il vostro codice. Ti faccio vedere un esempio (tratto da questo question):
foo (x:y:_) = x == y
foo [_] = foo []
foo [] = False
Secondo GHCi, il tipo di questa funzione è Eq a => [a] -> Bool
, come ci si aspetterebbe. Tuttavia, se si dichiara foo
con questa firma, si otterrà l'errore "Variabile di tipo ambiguo".
Il motivo per cui questa funzione funziona solo senza una firma di tipo è a causa di come funziona il controllo della digitazione in GHC. Quando si omette una firma del tipo, si assume che foo
abbia il monotipo [a] -> Bool
per alcuni tipi fissi a
. Una volta finito di digitare il gruppo di rilegatura, si generalizzano i tipi. Ecco dove ottieni il forall a. ...
.
D'altra parte, quando si dichiara una firma di tipo polimorfico, è dichiarare esplicitamente che foo
è polimorfica (e quindi il tipo di []
non deve corrispondere al tipo di primo argomento) e boom, si ottiene il tipo ambiguo variabile.
Ora, sapendo questo, confrontiamo il nucleo:
fib = 0:1:zipWith (+) fib (tail fib)
-----
fib :: forall a. Num a => [a]
[GblId, Arity=1]
fib =
\ (@ a) ($dNum :: Num a) ->
letrec {
fib1 [Occ=LoopBreaker] :: [a]
[LclId]
fib1 =
break<3>()
: @ a
(fromInteger @ a $dNum (__integer 0))
(break<2>()
: @ a
(fromInteger @ a $dNum (__integer 1))
(break<1>()
zipWith
@ a @ a @ a (+ @ a $dNum) fib1 (break<0>() tail @ a fib1))); } in
fib1
E per il secondo:
fib :: Num a => [a]
fib = 0:1:zipWith (+) fib (tail fib)
-----
Rec {
fib [Occ=LoopBreaker] :: forall a. Num a => [a]
[GblId, Arity=1]
fib =
\ (@ a) ($dNum :: Num a) ->
break<3>()
: @ a
(fromInteger @ a $dNum (__integer 0))
(break<2>()
: @ a
(fromInteger @ a $dNum (__integer 1))
(break<1>()
zipWith
@ a
@ a
@ a
(+ @ a $dNum)
(fib @ a $dNum)
(break<0>() tail @ a (fib @ a $dNum))))
end Rec }
Con firma di tipo esplicito, come con foo
sopra, GHC deve trattare fib
come valore potenzialmente ricorsivo polimorficamente. Abbiamo potuto passare un po 'diverso Num
dizionario per fib
in zipWith (+) fib ...
ea questo punto avremmo dovuto gettare la maggior parte della lista di distanza, dal momento che diversi Num
diversi mezzi (+)
. Naturalmente, una volta compilati con le ottimizzazioni, GHC rileva che il dizionario Num
non cambia mai durante le "chiamate ricorsive" e lo ottimizza.
Nel nucleo sopra, è possibile vedere che GHC fornisce effettivamente fib
un dizionario Num
(denominato $dNum
) ancora e ancora.
Poiché fib
senza firma tipo è stato ipotizzato monomorfa prima della generalizzazione di tutto il gruppo di legame era finito, le sottoparti fib
hanno avuto esattamente lo stesso tipo del tutto fib
. Grazie a questo, fib
assomiglia:
{-# LANGUAGE ScopedTypeVariables #-}
fib :: forall a. Num a => [a]
fib = fib'
where
fib' :: [a]
fib' = 0:1:zipWith (+) fib' (tail fib')
E perché il tipo rimane fissa, è possibile utilizzare solo quello dizionario data all'avvio.
A parte: ragionare sulle prestazioni in ghci è un po 'strano - è a) circa 30 volte più lento di ghc stesso, e b) qualsiasi codice del mondo reale utilizzerà ottimizzazioni, quindi le lezioni apprese in ghci non saranno così utili. –