Java
Javaでメソッドをカリー化してみる
関数をカリー化するには、その言語が第一級関数をサポートしている必要がある。
いわゆるC++などが持っているキャプチャ機能があるラムダ式(関数オブジェクト)とか、JavaScriptなどが持っているクロージャとかが使えれば可能だ。
Javaのメソッドは第一級関数ではないが、ラムダ式が使えるのでカリー化ができる。(関数型インターフェースを使う)
どうやら暗黙的にキャプチャしてくれるらしく、外側スコープの変数をラムダ式に閉じ込めることができる。(ローカル変数はイミュータブルの値キャプチャ、インスタンス変数は参照先ミュータブルの参照キャプチャになるらしい)
この機能を使うと、メソッドのカリー化ができる。
JavaはC++と違って、引数や戻り値の有無・引数の個数によって、使用する関数型インターフェースを使い分けなくてはならないようだ。
おまけにそのラムダ式を呼び出すときのメソッド名がバラバラだ。
例えば引数1つで戻り値があるラムダ式を使いたい時は、Functionインターフェースを使いapply()メソッドを呼ぶ、という感じになる。
引数が2つあるときはBiFunctionインターフェースだし、引数が1つで戻り値が無いものが使いたいなら、Consumerインターフェースを使いaccept()メソッドを呼ぶことになる。
こういった色々な引数と戻り値の組み合わせに対応できるようにするため、かなりの数の関数型インターフェースが用意されている。
なので、ラムダ式を生成するときはスラッと書けるのだが、そのラムダ式を関数から返すときはインターフェース名を調べ、ラムダ式を呼び出したいときはメソッド名を調べ・・という感じだ。(暗記力のある人は全パターンを覚えられるのかもしれないが)
この辺がC++に慣れている人には結構なストレスになる。(Rust、Go、Swift、Kotlinなど、モダンな言語に慣れている人もそうだと思う)
C++のラムダ式は関数オブジェクトのシンタックスシュガーであり、唯一のメンバ関数を()演算子で呼び出せるようにオーバーロードしているので、生成も呼び出しも、あたかも普通の関数であるかのように書ける。
Javaのラムダ式は「特定のインターフェースを継承した無名クラスをその場に書き、そのインスタンスを生成する」という書き方のシンタックスシュガーで、生成時はいい感じにラムダ式している書き方ができる。(JavaScriptのアロー関数並みに書きやすい)
しかしJavaは演算子のオーバーロードが仕様上できないので、呼び出し時はどうしてもインスタンスのメソッドを呼び出す形で書くしかない。
話が逸れたが、下のような「2つの値を受け取って、その和を返す」というシンプルなメソッドをカリー化してみる。(クラスは省略している)
これをJavaでカリー化すると、下の「addNMethod」のようになる。
部分適用するためのラムダ式は「引数が1つ、戻り値あり」の関数なので、Functionインターフェースを使い、受け取った側でapply()メソッドを呼び出す。
|