将棋でもっとも大事なのは、一つの場面で正確な判断を下すことだ。その基準を瞬時に見いださなければならない。
対局中には、これまでの経験から、たくさんの知識がばらばらに思い浮かぶ。
だが、それらはジグソーパズルのピースのようなものなので、そのままでは役に立たない。それを「これはいい手だ」「悪い手だ」とか「どういう流れだ」と瞬時に判断し、次の一手を選択しなければならない。
方向性やプランに基づいて、ばらばらの知識のピースを連結するのが知恵の働きである。
⇧ 『決断力(著:羽生善治)』の一節ですね 、あっ!どうも、ボクです。
複数のものを、まとめ上げていく、その行為を、ジグゾーパズルになぞらえているようですね、名人!
というわけで、今回は、Java のお話です。
関数型インターフェイスって?
そもそも、関数型インターフェイスとは?
⇧ 上記サイト様が詳しいです。
また、
ラムダ式の型は何なのでしょう?一部の言語では、関数の値や関数オブジェクトを使ってラムダ式を表現しますが、Java 言語では違います。Java では、関数型インターフェースを使用してラムダ式の型を表現します。初めは奇妙に思えるかもしれませんが、実のところ、これは Java の古いバージョンとの後方互換性を確実にする効率的な方法なのです。
⇧ 上記サイト様によりますと、「関数型インターフェイス」っていうのは、Java 独自の仕様らしいですね。
Java 8 以前との互換性のために、こういう形で導入されたようです。
Oracle JDK のOracleさんのドキュメントの説明では、
関数型インタフェースは、ラムダ式やメソッド参照のターゲットとなる型を提供します。
各関数型インタフェースには、その関数型インタフェースの関数メソッドと呼ばれる単一の抽象メソッドが含まれており、ラムダ式のパラメータや戻り値の型のマッチングや適応は、そのメソッドに対して行われます。
関数型インタフェースは代入の文脈、メソッド呼び出しの文脈、キャストの文脈など、さまざまな文脈でターゲットの型を提供できます。
⇧ ラムダ式やメソッド参照を実装する上で、必要になってくる型として存在しているのだと。
まとめると、関数型インターフェイスとは、
- 関数型インターフェイスは、ラムダ式やメソッド参照を受け持つ入れ物(参照変数)としての役割を持つ
- 関数型インターフェイスは、抽象メソッドを1つだけ必ず定義する必要がある(逆に言えば、抽象メソッドは1つしか定義しちゃ駄目)
- 関数型インターフェイスは、ほぼ、どこでも使用できる
ってことですかね...よく分からんけど。
ラムダ式、とか、メソッド参照、とか...って何なの?
そんじゃあ、「ラムダ式」、「メソッド参照」って何なのよ?
Wikipediaさんによりますと、
プログラミング言語における無名関数(英語: anonymous functionあるいはnameless function)とは、名前付けされずに定義された関数のことである。無名関数を表現するための方法には様々なものがあるが、近年主流となっているのはラムダ式による記法である。無名関数を表現するリテラル式は、関数リテラル (function literal) とも呼ばれる。値がある場合は関数オブジェクトであるものが多い。
⇧ 「無名関数」の1種であるらしいと。要するに、関数らしい。
なので、
「ラムダ式」∈「無名関数」
ってことが言える?ということで、「ラムダ式」は、関数であるから、Javaでは「関数型インターフェイス」を実装する必要があるってことになるらしい。
つまり、「関数型インターフェイス」が、何故にJava 8 で登場したかというと、Java でラムダ式を実現したかったからと言っても過言では無いのではないかと。
では、「メソッド参照」は?
⇧ 上記サイト様が詳しいです。
で、どういう時に「メソッド参照」を使うのよ?
⇧ と前置きして、
List<
Integer
> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.stream()
.filter(e -> e % 2 == 0)
.forEach(e -> System.out.println(e));
⇧ 上記のような例を提示し、
forEach
メソッドに渡されるラムダ式が、パススルー・ラムダ式と呼ばれるものです。式 e -> System.out.println(e)
はそのパラメーターを、PrintStream
クラスの println
メソッド (System.out
のインスタンス) に引数として渡します。
⇧ こういったケースのラムダ式においては、
パラメーターで実際の作業を行わないパススルー・ラムダ式は、メソッド参照で置き換えるほうが有益です。
⇧ ということらしく、
numbers.stream()
.filter(e -> e % 2 == 0)
.forEach(System.out::println);
⇧ のように、置き換えたほうが可読性も上がるでしょう、ということらしい。
「ラムダ式」だと冗長になりそうな時~、「メソッド参照」を使うべしってことかしらね...。
カリー化って?部分適用と混同されやすいらしい...
関数型インターフェイスが、「ラムダ式」や「メソッド参照」を実現する、つまり、Javaで関数型プログラミング的なことを実現するためのものとして導入されたんであると。
ということは、「カリー化」ができてしまうというわけですか?
カリー化って何じゃらほい?
カリー化 (currying, カリー化された=curried) とは、複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。
⇧ 何だろう、説明がむちゃくちゃ分かりにくいんだけど...
カリー化は、複数引数を取る関数(例えばAとBを取ってCを返す関数)を、一つの関数を取って「関数」を返す関数にすること(Aを取って「Bを取ってCを返す関数」を返す関数)です。
カリー化したところで、AとBを渡さないとCを得ることはできません。
それに対して、部分適用はAとBが必要な関数に例えばAを(先に)渡してしまって、Bを取ってCを返す関数にすることです。
⇧ 上記サイト様のコメントの方の説明が分かりやすいです。
具体例としては、
⇧ JavaScriptのコードですが、上記サイト様のコード例が、「カリー化」とはどういうものかを理解するのに、分かりやすいです。
Java でカリー化
というわけで、Java 8 で関数型インターフェイスが導入され、「カリー化」が実現できるということなので、試してみますか。
⇧ 上記サイト様を参考にさせていただきました。
Eclipse を起動し、適当なJavaプロジェクトを作成し、クラスも作成で。
ソースはこんな感じになりました。
package main; import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; public class TestCurry { public static void main(String[] args) { // カリー化のメソッドを実装 Function<Integer, Function<Integer, Integer>> sum = curried(TestCurry::sum); // Optionalな値を用意 Optional<Integer> a = Optional.ofNullable(80); Optional<Integer> b = Optional.ofNullable(20); Optional<Integer> c = Optional.empty(); // Optionalな値で関数を実行するメソッドの結果を出力 System.out.println(apply(apply(Optional.of(sum), a), b)); System.out.println(apply(apply(Optional.of(sum), a), c)); System.out.println(apply(apply(Optional.empty(), a), b)); // Optionalな値で関数を実行するメソッドの結果を値にして出力 System.out.println(apply(apply(Optional.of(sum), a), b).orElse(0)); System.out.println(apply(apply(Optional.of(sum), a), c).orElse(0)); System.out.println(apply(apply(Optional.empty(), a), b).orElse(0)); // カリー化のメソッドのみを使用した結果を出力 System.out.println(sum.apply(a.orElse(0)).apply(b.orElse(0))); System.out.println(sum.apply(a.orElse(0)).apply(c.orElse(0))); } // カリー化のメソッド static <T, U, R> Function<T, Function<U, R>> curried(BiFunction<T, U, R> biFunction) { return (t) -> (u) -> biFunction.apply(t, u); } // 合計するメソッド(カリー化の対象メソッド) static int sum(int a, int b) { return a + b; } // 渡された関数を、渡された引数(Optionalな値)で実行するメソッド static <A, B> Optional<B> apply(Optional<Function<A, B>> func, Optional<A> param) { return param.flatMap((p)-> func.map((f) -> f.apply(p))); } }
んで、実行してみる。
orElse() に、「0」を入れてるのは、「null」を入れてしまうと、2回目のapplyメソッドの実行で、null.apply() で実行される可能性がある、つまり、NullPointerException で落ちるからですかね。まぁ、このへんの実装も、う~ん、な気がしますが、他に思いつかんかった...
まぁ、何て言うか、関数型インターフェイスの記述、慣れる気がしない...
今日もまたモヤモヤ感が...
今回はこのへんで。