関数についてもっと
型変数
以下のような型シグネチャを持つ関数を考えてみましょう:
indexOf : String -> List String -> Int
ここで仮定した関数は、文字列と文字列のリストを取り、指定された文字列がリスト内で見つかった場合はインデックスを、見つからない場合は-1を返します。 もちろん、文字列のリストではなく整数リストに対しては、この関数を使用することはできません。
しかし、特定の型の代わりに型変数またはスタンドインを使用することによって、この関数をジェネリックにすることができます。
indexOf : a -> List a -> Int
String
をa
で置き換えることにより、 今やindexOf
のシグネチャは「任意の型a
の値と同じ型a
のリストをとり、整数を返す」というものになります。型が一致する限り、コンパイラは満足します。indexOf
は今や、引数「String
とString
のリスト」でも「Int
とInt
のリスト」でも呼び出すことができ、期待するように動作します。
この方法で関数をよりジェネリックにできます。複数の型変数を持つこともできます:
switch : ( a, b ) -> ( b, a )
switch ( x, y ) =
( y, x )
この関数は(a,b)
型のタプルをとり、(b,a)
型のタプルを返します。次はすべて有効な呼び出しです。
switch (1, 2)
switch ("A", 2)
switch (1, ["B"])
型変数には任意の小文字の識別子を使用でき、 a
とb
のように1文字にするのは慣習にすぎません。たとえば、次のシグネチャは完全に有効です。
indexOf : thing -> List thing -> Int
引数としての関数
次のようなシグネチャを考えてみましょう:
map : (Int -> String) -> List Int -> List String
この関数は:
- 引数として関数「
(Int -> String)
」と整数のリストをとり、 - そして文字列のリストを返します。
興味深いのは(Int -> String)
の部分です。これは、引数の関数が (Int -> String)
シグネチャに従わなければならないことを示しています。
例えば、coreにある toString
はそのような関数です。したがって、この map
関数を以下のように呼び出すことができます:
map toString [1, 2, 3]
しかし、 Int
とString
は特殊すぎます。代りに型変数を使ったシグネチャを良く見るでしょう。
map : (a -> b) -> List a -> List b
この関数は a
のリストをb
のリストにマップします。a
とb
が実際にどのような型であるかは、最初の引数に与えられた関数が同じ型を使用している限り気にしません。
たとえば、次のシグネチャを持つ関数があるとき、
convertStringToInt : String -> Int
convertIntToString : Int -> String
convertBoolToInt : Bool -> Int
ジェネリックなmapは次のように呼び出すことができます:
map convertStringToInt ["Hello", "1"]
map convertIntToString [1, 2]
map convertBoolToInt [True, False]