読者です 読者をやめる 読者になる 読者になる

Functional Java で List モナド - Groovy, Java 8 Lambda

Java Groovy Monad

id:fits:20120912 にて Scalaz で実装したナイト移動の List モナド処理を Functional Java を用いて Groovy と Java SE 8 Early Access with Lambda Support(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 を fj.Show.show クラスメソッドに fj.F を渡して作成しています。

ちなみに、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

*1:@Immutable とした事で x と y の値が等しいオブジェクトは == で true となります

*2:id:fits:20120912 の実装内容と同等

*3:fj.F クラスの f メソッドとは別物です