明示的な型アノテーションを与えることにより、GHCがコードについて特定の仮定をするのを防ぎます。例を示します(この質問から抜粋):
foo (x:y:_) = x == y
foo [_] = foo []
foo [] = False
GHCiによると、この関数のタイプは、ご想像のとおりEq a => [a] -> Boolです。ただし、この署名を使用して宣言するfooと、「あいまいな型変数」エラーが発生します。
この関数が型シグネチャなしでのみ機能する理由は、GHCでの型チェックの機能によるものです。型シグネチャを省略すると、一部の固定型に対してfooモノタイプであると見なされます。バインディンググループの入力が終了したら、タイプを一般化します。それはあなたが得るところです。[a] -> Boolaforall a. ...
一方、ポリモーフィック型シグネチャを宣言する場合、それfooがポリモーフィックであり(したがって、[]の型は最初の引数の型と一致する必要はありません)、ブームであると明示的に述べると、あいまいな型変数が得られます。
さて、これを知って、コアを比較してみましょう:
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
そして2番目のもののために:
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 }
上記のように、明示的な型アノテーションを使用するとfoo、GHCはfib潜在的に多形的に再帰的な値として処理する必要があります。にいくつかの異なる辞書を渡すことができます。異なるとは異なることを意味するため、この時点でリストの大部分を破棄する必要があります。もちろん、最適化を使用してコンパイルすると、GHCは、「再帰呼び出し」中に辞書が変更されないことに気付き、それを最適化します。NumfibzipWith (+) fib ...Num(+)Num
fib上記のコアでは、GHCが実際にNum(という名前の)辞書$dNumを何度も提供していることがわかります。
fibバインディンググループ全体の一般化が完了する前は、型アノテーションがないと単形であると想定されていたため、サブfibパーツには全体とまったく同じ型が与えられましたfib。これのおかげで、fib次のようになります:
{-# LANGUAGE ScopedTypeVariables #-}
fib :: forall a. Num a => [a]
fib = fib'
where
fib' :: [a]
fib' = 0:1:zipWith (+) fib' (tail fib')
また、タイプは固定されているため、最初に指定された1つの辞書だけを使用できます。