明示的な型アノテーションを与えることにより、GHCがコードについて特定の仮定をするのを防ぎます。例を示します(この質問から抜粋):
foo (x:y:_) = x == y
foo [_] = foo []
foo [] = False
GHCiによると、この関数のタイプは、ご想像のとおりEq a => [a] -> Bool
です。ただし、この署名を使用して宣言するfoo
と、「あいまいな型変数」エラーが発生します。
この関数が型シグネチャなしでのみ機能する理由は、GHCでの型チェックの機能によるものです。型シグネチャを省略すると、一部の固定型に対してfoo
モノタイプであると見なされます。バインディンググループの入力が終了したら、タイプを一般化します。それはあなたが得るところです。[a] -> Bool
a
forall 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は、「再帰呼び出し」中に辞書が変更されないことに気付き、それを最適化します。Num
fib
zipWith (+) 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つの辞書だけを使用できます。