Javaで関数合成っぽい何か

C#で関数合成 - Qiita

ネタとしては新しいものではないけど見かけたのでJavaでやる。

残念ながらJava8は都市伝説でJava6が相変わらずナウでヤングな環境で暮らしているのでJava6前提。

C#のFuncに相当するものがないのでそれをまず用意。

public interface Func1<A, R> {
    R apply(A a);
}

型パラメータの書き方はその人の歩んできた道が少し見える気もしなくはない。

次に拡張メソッドでやっている事をそれっぽいクラスで表現

public final class Composition<A, B, R> {

    public static <A, B, R> Func1<A, R> andThen(final Func1<A, B> func1, final Func1<B, R> func2) {
        return new Func1<A, R>() {
            @Override
            public R apply(A a) {
                return func2.apply(func1.apply(a));
            }
        };
    }
}

んで実行側

    public static void main(String[] args) {
        Func1<Integer, String> itos = new Func1<Integer, String>() {
            @Override
            public String apply(Integer i) {
                return i.toString();
            }
        };

        Func1<String, String> repeat = new Func1<String, String>() {
            @Override
            public String apply(String s) {
                return s + s;
            }
        };

        String ans = andThen(itos, repeat).apply(10);
        System.out.println(ans); // 1010
    }

なんか色々残念な感じですが、だいたいこんな感じですよね。

素敵なJava8パワーだとほとんど同じに書けますね。

public interface Func1<A, B> {
    B apply(A a);

    default <C> Func1<A, C> andThen(final Func1<B, C> f) {
        return a -> f.apply(apply(a));
    }
}

default実装のお陰でCompositionクラスが不要になり呼び出し元もC#と同じ感じに。

    public static void main(String[] args) {
        Func1<Integer, String> itos = i -> i.toString();

        Func1<String, String> repeat = s -> s + s;

        String ans = andThen(itos, repeat).apply(10);
        System.out.println(ans); // 1010

        String ans2 = itos.andThen(repeat).apply(10);
        System.out.println(ans2); // 1010

    }

ちなみに書き方イマイチ分かってなかったですが、IntelliJ先生が全て直してくれました。

  • 追記1(2014.09.05)

$は普通に使える文字なんですね。
恥ずかしい><

そういえばJavaは全角文字もメソッドに使えましたね!!記号もばっちりです!!

public interface Func1<A, B> {
    B apply(A a);

    default <C> Func1<A, C> andThen(final Func1<B, C> f) {
        return a -> f.apply(apply(a));
    }

    default <C> Func1<A, C> $(final Func1<B, C> f) {
        return a -> f.apply(apply(a));
    }
}

実行側

    public static void main(String[] args) {
        Func1<Integer, String> itos = i -> i.toString();

        Func1<String, String> repeat = s -> s + s;

        String ans = andThen(itos, repeat).apply(10);
        System.out.println(ans); // 1010

        String ans2 = itos.andThen(repeat).apply(10);
        System.out.println(ans2); // 1010 

        String ans3 = itos.$(repeat).apply(10);
        System.out.println(ans3); // 1010

    }

追記2(2014.09.05)
インターフェースではなく抽象クラスを使えばそれっぽくはなりますね。

public abstract class F1<A, B> {
    public abstract B apply(A a);

    public <C> F1<A, C> andThen(final F1<B, C> f) {
        final F1<A, B> f1 = this;
        return new F1<A, C>() {
            @Override
            public C apply(A a) {
                return f.apply(f1.apply(a));
            }
        };
    }
}
  • 使う側
        F1<Integer, Integer> mul5 = new F1<Integer, Integer>() {
            @Override
            public Integer apply(Integer i) {
                return i * 5;
            }
        };

        F1<Integer, Integer> add10 = new F1<Integer, Integer>() {
            @Override
            public Integer apply(Integer i) {
                return i + 10;
            }
        };

        System.out.println(mul5.andThen(add10).apply(10)); //10 * 5 + 10 -> 60

Compositionクラスみたいな感じでやった場合だと連続して適用しようとすると型パラメータ制約満たせないよ!みたいなエラーになって未熟な自分には解決できなかった。

抽象クラスでやるデメリットとしては、Java8が使えるようになった場合に、ラムダ式使えなかったりとか普通の抽象クラスと同じ制約があるとかそんな感じかな?
前者はinterfaceに変えてメソッドをdefaultにすればいい感じもしなくはない。

追記終わり。

java.util.funciton.Functionを使ってないとかはまぁ気にしない。
なんかそれっぽい感じを醸し出せたので終わります。

これはお遊びでしたが、Functional Javaなんてのもありますね。
Functional Java - Documentation

Scalaのすごい人のスライド
functional java に関するメモ(気まぐれに随時更新?) - scalaとか・・・
Functional Javaの発表してきた - scalaとか・・・

その他参考
O'Reilly Japan - Java開発者のための関数プログラミング

OCmal

# let ($) f g h = g(f(h));;
val ( $ ) : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c = <fun>
# let r = string_of_int $ (fun x -> x ^ x);;
val r : int -> string = <fun>
# r 10;;
- : string = "1010"