Functional Java で List モナド - Groovy, Java 8 Lambda
id:fits:20120912 にて Scalaz で実装したナイト移動の List モナド処理を Functional Java を用いて Groovy と Java SE 8 Early Access with Lambda Support(b56) で実装してみました。
使用した環境は以下の通り。
- Functional Java 3.1
- Groovy 2.0.4
- OpenJDK 1.8.0-ea-lambda b56
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20120930/
Groovy の場合
基本的に Functional Java では fj.F クラス (引数の数に応じて fj.F2 から fj.F8 まであり、全て abstract クラス) の f メソッドを実装する事で "関数" を定義します。
Groovy では f メソッドのシグネチャに合致するクロージャを as fj.F すれば fj.F オブジェクトが得られるので、それを bind や filter メソッドの引数に渡しています。
また、ナイトの位置情報を表す KnightPos は @Immutable なクラスとして実装しています。 *1
3手版 move_knight.groovy
@Grab("org.functionaljava:functionaljava:3.1") import groovy.transform.Immutable import fj.F import static fj.data.List.* @Immutable class KnightPos { int x int y } // KnightPos の生成 def pos = {x, y -> new KnightPos(x: x, y: y)} // ナイトの次の移動先を列挙 def moveKnight = {KnightPos p -> list( pos(p.x + 2, p.y - 1), pos(p.x + 2, p.y + 1), pos(p.x - 2, p.y - 1), pos(p.x - 2, p.y + 1), pos(p.x + 1, p.y - 2), pos(p.x + 1, p.y + 2), pos(p.x - 1, p.y - 2), pos(p.x - 1, p.y + 2) ).filter({ 1<= it.x && it.x <= 8 && 1 <= it.y && it.y <= 8 } as F) } as F // 3手先の移動位置を列挙 def in3 = {KnightPos p -> list(p).bind(moveKnight).bind(moveKnight).bind(moveKnight) } // 指定位置に3手で到達できるか否かを判定 def canReachIn3 = {KnightPos start, KnightPos end -> in3(start).exists({ it == end } as F) } println in3(pos(6, 2)) println canReachIn3(pos(6, 2), pos(6, 1)) println canReachIn3(pos(6, 2), pos(7, 3))
実行結果は以下の通り。
3手版の実行結果
> groovy move_knight.groovy <KnightPos(8, 1),KnightPos(8, 3),・・・ ・・・ ,KnightPos(3, 4),KnightPos(3, 8)> true false
次は N手版です。
モナディック関数の合成箇所 (inMany の処理) は foldLeft1 と fj.F2 を使って実装しています。foldLeft1 は初期値を別途指定する必要がなく、リストの最初の値が初期値として使用されます。
処理内容は、x 個からなる moveKnight の List を foldLeft1 で関数合成し、初期位置 p の List へ bind しています。*2
N手版 move_knight_many.groovy
@Grab("org.functionaljava:functionaljava:3.1") import groovy.transform.Immutable import fj.F import fj.F2 import static fj.data.List.* ・・・ // x手先の移動位置を列挙 def inMany = {int x, KnightPos p -> list(p).bind replicate(x, moveKnight).foldLeft1({b, a -> { n -> b.f(n).bind(a) } as F } as F2) } // 指定位置にx手で到達できるか否かを判定 def canReachIn = {int x, KnightPos start, KnightPos end -> inMany(x, start).exists({ it == end } as F) } println inMany(3, pos(6, 2)) println canReachIn(3, pos(6, 2), pos(6, 1)) println canReachIn(3, pos(6, 2), pos(7, 3))
N手版の実行結果
> groovy move_knight.groovy <KnightPos(8, 1),KnightPos(8, 3),・・・ ・・・ ,KnightPos(3, 4),KnightPos(3, 8)> true false
Java SE 8 Early Access with Lambda Support(b56)の場合
次に、Java SE 8 Early Access のラムダ式を使って実装してみます。
ちなみに、現時点での Java ラムダ式の仕様は Groovy のクロージャによく似ています。
Java ラムダ式の例
() -> 11 (int x) -> x + 1 (int x) -> { return x + 1; } (x) -> x + 1 x -> x + 1
ただし今のところ、ラムダ式は abstract メソッドの実装には使えないようでしたので、
ラムダ式を適用するインターフェース Func と、Func から fj.F オブジェクトを作成する f クラスメソッド *3 を別途用意しました。 (他にもっと良い方法があるかもしれません)
これで、f クラスメソッドにラムダ式を渡せば、ラムダ式が Func インターフェースの実装オブジェクトとして扱えます。
Functional Java で出力内容を制御するには基本的に fj.Show クラスを使うようですので、KnightPos の内容を出力するための fj.Show
ちなみに、KnightPos は fj.P2 のサブクラスとして実装しています。
3手版 MoveKnight.java
import fj.*; import fj.data.*; import static fj.P.*; import static fj.Show.*; import static fj.data.List.*; class MoveKnight { public static void main(String[] args) { // KnightPos 用の Show 作成 Show<KnightPos> ks = show( f( (KnightPos p) -> Stream.fromString("(" + p._1() + ", " + p._2() + ")") ) ); listShow(ks).println( in3(pos(6, 2)) ); booleanShow.println( canReachIn3(pos(6, 2), pos(6, 1)) ); booleanShow.println( canReachIn3(pos(6, 2), pos(7, 3)) ); } // ナイトの次の移動先を列挙 static F<KnightPos, List<KnightPos>> moveKnight() { return f( (KnightPos p) -> list( pos(p._1() + 2, p._2() - 1), pos(p._1() + 2, p._2() + 1), pos(p._1() - 2, p._2() - 1), pos(p._1() - 2, p._2() + 1), pos(p._1() + 1, p._2() - 2), pos(p._1() + 1, p._2() + 2), pos(p._1() - 1, p._2() - 2), pos(p._1() - 1, p._2() + 2) ).filter( f( (KnightPos t) -> 1<= t._1() && t._1() <= 8 && 1 <= t._2() && t._2() <= 8 ) ) ); } // 3手先の移動位置を列挙 static List<KnightPos> in3(KnightPos start) { return list(start).bind(moveKnight()).bind(moveKnight()).bind(moveKnight()); } // 指定位置に3手で到達できるか否かを判定 static boolean canReachIn3(KnightPos start, KnightPos end) { return in3(start).exists( f( (KnightPos p) -> p._1() == end._1() && p._2() == end._2() ) ); } // fj.F のインスタンスを生成 static <A, B> F<A, B> f(final Func<A, B> func) { return new F<A, B>() { @Override public B f(A a) { return func.apply(a); } }; } // KnightPos の生成 static KnightPos pos(int x, int y) { return new KnightPos(x, y); } // ラムダ式が適用されるインターフェース interface Func<A, B> { B apply(A a); } static class KnightPos extends P2<Integer, Integer> { private int x; private int y; KnightPos(int x, int y) { this.x = x; this.y = y; } @Override public Integer _1() { return x; } @Override public Integer _2() { return y; } } }
実行結果は以下の通り。
Functional Java の JAR ファイルは Groovy の @Grab で取得したものを使用しました。
3手版の実行結果
> javac -cp .;functionaljava-3.1.jar MoveKnight.java > java -cp .;functionaljava-3.1.jar MoveKnight <(8, 1),(8, 3),・・・ ・・・ ・・・,(3, 4),(3, 8)> true false
最後に Java の N手版です。
モナディック関数の合成箇所 (inMany 内の処理) は基本的に Groovy 版と同じですが、
fj.F2 用に Func2 インターフェースと f2 クラスメソッドを追加しています。
N手版 MoveKnightMany.java
import fj.*; import fj.data.*; import static fj.P.*; import static fj.Show.*; import static fj.data.List.*; class MoveKnightMany { public static void main(String[] args) { Show<KnightPos> ks = show( f( (KnightPos p) -> Stream.fromString("(" + p._1() + ", " + p._2() + ")") ) ); listShow(ks).println( inMany(3, pos(6, 2)) ); booleanShow.println( canReachInMany(3, pos(6, 2), pos(6, 1)) ); booleanShow.println( canReachInMany(3, pos(6, 2), pos(7, 3)) ); } ・・・ // x手先の移動位置を列挙 static List<KnightPos> inMany(int x, KnightPos start) { return list(start).bind( replicate(x, moveKnight()).foldLeft1( f2( (F<KnightPos, List<KnightPos>> b, F<KnightPos, List<KnightPos>> a) -> f( (KnightPos n) -> b.f(n).bind(a) ) ) ) ); } // 指定位置にx手で到達できるか否かを判定 static boolean canReachInMany(int x, KnightPos start, KnightPos end) { return inMany(x, start).exists( f( (KnightPos p) -> p._1() == end._1() && p._2() == end._2() ) ); } ・・・ static <A, B, C> F2<A, B, C> f2(final Func2<A, B, C> func) { return new F2<A, B, C>() { @Override public C f(A a, B b) { return func.apply(a, b); } }; } ・・・ interface Func2<A, B, C> { C apply(A a, B b); } ・・・ }
N手版の実行結果
> javac -cp .;functionaljava-3.1.jar MoveKnightMany.java > java -cp .;functionaljava-3.1.jar MoveKnightMany <(8, 1),(8, 3),・・・ ・・・ ・・・,(3, 4),(3, 8)> true false