Scalaにおいて型推論を利用する際にカリー化が利用されることを知ったので、まとめておきます。
型推論について
型推論とは静的型付け言語における言語機能であり、明示的に型を記述しなくてもコンパイラが自動的に型を決定してくれる機能です。
val x = 100 // 型Intを指定しなくても、代入される値からコンパイラが変数xの型を自動で決定する
動的型付けとの違いに関しては、動的型付けと型推論の違いに書いたので参照してみてください。
カリー化について
カリー化 (currying, カリー化された=curried) とは、複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。
カリー化 - Wikipedia
カリー化とは複数の引数をとる関数があった場合に、その関数を部分的に引数を受け取ることが出来る関数に変換することです。
誤解される表現として部分適用という言葉がありますが、これはカリー化された関数の一部に引数を渡すことで部分的に引数が適用された関数を生成することです。
そのため部分適用を行う前段階がカリー化ということになります。
val add = (a:Int, b: Int) => a+b // カリー化されていない関数
val curriedAdd = (a:Int) => (b:Int) => a+b // カリー化された関数
val addOne = curriedAdd(1) // カリー化された関数に対して部分適用をすることで、新たな関数を生成
println( addOne(2) ) // 3が出力される
カリー化による型推論
下記のようなカリー化されていない関数に対して関数fを無名関数として渡す場合は、引数の型を指定する必要があります。
def dropWhile[A](l: List[A], f: A => Boolean): List[A]
val list: List[Int] = List(1, 2, 3, 4, 5)
val result = dropWhile(list, (x: Int) => x < 4))
もし引数の型を指定せずに実行した場合は、missing parameter type
というエラーが発生します。
val list: List[Int] = List(1,2,3,4,5)
val result = dropWhile(list, x => x < 4))
dropWhileの第1引数list
はList[Int]であるので、第2引数x
の型がIntであることは明らかです。そのため無名関数fのx
の型を示すのは、少し不自然に感じます。
この問題を解決するには、dropWhileが引数を部分的に受け取るようにする(カリー化)ことで、Scalaはx
の型を推論できるようになります。
def dropWhile[A](l: List[A])(f: A => Boolean): List[A] // dropWhileをカリー化する
val list: List[Int] = List(1, 2, 3, 4, 5)
val result = dropWhile(list)(x => x < 4) // 型推論が可能となり、型の指定が必要なくなる
カリー化されたdropWhileの呼び出しでは、最初にdropWhile(list)が関数fを引数として受け取る関数を返します。この関数は最初に渡された引数list
の型List[Int]により、型パラメーターAがIntとして解釈されます。
そのため無名関数fを引数として渡す際にx
の型をコンパイラが推論可能となり型を省略して記述することが可能となります。
他の関数型言語について
上記のカリー化と型推論の話はScalaにおける話であり、関数型言語共通の話では無いようです。
Haskell や OCaml といった他の関数型言語では、完全な推論が提供されるため、型アノテーションが必要になるケースはほぼ存在しない。
Scala関数型デザイン&プログラミング ―Scalazコントリビューターによる関数型徹底ガイド
参考文献
Scala関数型デザイン&プログラミング ―Scalazコントリビューターによる関数型徹底ガイド
http://book.impress.co.jp/books/1114101091