たとえばHaskellやMLの関数f1とf2を考えてみましょう。f2がf1を呼び出すとします。型推論は、f1が呼び出すことができる情報を使用するだけですか。これは、f1の定義を確認することを意味します。または、f1で型推論を行うときにf2がf1を呼び出す方法も調べますか。たとえば、f2はf1を呼び出すときにリテラルを渡す可能性があり、これによりf1の引数タイプが制限されます。
2 に答える
これは、関数f1
が宣言によってバインドされているか、他の手段によってバインドされているかによって異なります(たとえば、への関数パラメーターとしてf2
)。前者の場合、そのタイプは多形になるように一般化できますが、後者の場合はできません。未解決の部分はコンテキストによって決定されます。また、前者の場合でも、MLの値制限などの追加のルールが適用される場合があります。
Haskellのこの例を考えてみましょう:
f1 = \x -> x -- polymorphic: f1 :: a -> a
f2 = f1 True -- instantiates f1 :: Bool -> Bool
f2 = let f1 = \x -> x in f1 True -- likewise
f2 = (\f1 -> f1 True) (\x -> x) -- here, f1 cannot be polymorphic,
-- so the lambda is restricted to Bool -> Bool by the call
同様にSMLでも:
val f1 = fn x => x (* polymorphic, f1 : 'a -> 'a *)
val f2 = f1 true
val f2 = let val f1 = fn x => x in f1 true end (* likewise *)
val f2 = (fn f1 => f1 true) (fn x => x) (* f1 monomorphic, f1 : bool -> bool *)
val f1 = let _ = 0 in fn x => x end (* value restriction applies, f1 cannot be polymorphic *)
val f2 = f1 true (* determines type f1 : bool -> bool *)
わかりやすくするために、ここでは省略された関数宣言構文を使用していません。
型システムは、呼び出し元に関する情報を使用して、関数が処理できる型を判別しません。これは、過度に制限的であり、一般に達成することは不可能です。たとえば、(Haskell)
aList :: [Int]
aList = [1,2,3]
one = head aList
今後、head
from[a] -> a
から[Int] -> Int
;のタイプを制限します。その後はhead ["hello", "world"]
不可能になりhead
、次に別のタイプで使用するときに再定義する必要があります。ただし、の定義のコンテキストでは、その型の変数がインスタンス化されるためone
、head
実際には型があります。しかし、それはのグローバル定義やそのタイプ[Int] -> Int
を変更しません。head
(実際には、コンパイラーは、プログラムのセマンティクスを変更しない限り、いくつかの状況でのみ呼び出されることがわかっている関数を特殊化し、渡される特定の型にコードを適合させることができます。)