CLOVER🍀

That was when it all began.

Javaプログラマ向け高階関数紹介

Javaプログラマ向けに、高階関数のお話でも。

高階関数とは、「関数を引数に取る関数」のことです。本来は「関数を返す関数」(カリー化)も含むそうですが、高階関数というと専らこの意味で使われることが多いので問題ないでしょう。

Javaでは、メソッドとかの引数の最小単位はクラス(のインスタンス)もしくはプリミティブなので、他の言語で登場する関数渡しとかクロージャーとかは、Javaしか知らないプログラマからすると、相当疎遠な存在ではないかと思います。

自分も、Scalaを勉強するまではそんな感じでした。そして、Scalaを知ってからちょっとした衝撃を受けたわけですが。

今回は、PythonとJavaを対比して紹介したいと思います。リストの反復処理(foreach)と、リストから新しいリストを生成する処理、写像(map)の例でいきます。

まずは、Pythonコードから。まずは、原型となる関数、my_foreachとmy_mapを宣言します。ちなみに、標準のCentOSでやっているので、Pythonのバージョンは2.4.3ですよ。
※my_が付いているのは、Pythonには標準でmap関数があるからです

def my_foreach(seq, func):
    for s in seq:
        func(s)

def my_map(seq, func):
    newSeq = []

    for s in seq:
        newSeq.append(func(s))

    return newSeq
    # return [func(s) for s in seq]  ## 上記コードはこれと等価

my_mapはfor内包表記を利用すればずっと短くなりますが、Pythonを知らなくてもわかるような表記にしました。

続いて、処理の元ネタとなるリストを宣言。

xs = [1, 2, 3, 4, 5]

my_mapとmy_foreachに適用する関数を宣言し、適用してみます。my_mapには要素を3倍して返す関数tripleを渡し、my_foreachには単純に要素を出力するだけのprint_elem関数を渡します。

def triple(x): return x * 3
def print_elem(x): print x,

print my_map(xs, triple)   # -> [3, 6, 9, 12, 15]
my_foreach(xs, print_elem)  # -> 1 2 3 4 5

my_map、my_foreachを呼び出しているコードの隣にコメントで結果を書いていますが、関数の引数に関数を渡して処理を実行しています。

こんな短い処理に、いちいち関数を宣言するのは嫌だ!という方は、lambdaを利用すれば匿名関数を宣言できるので、それをmy_mapおよびmy_foreachに渡します。

import sys
print my_map(xs, (lambda x: x * 3))  # -> [3, 6, 9, 12, 15]
my_foreach(xs, (lambda x: sys.stdout.write(str(x)))) # -> 12345

先ほどの例と、同様の動きをします。
※Pythonのprintは関数ではなく文なので、lambdaで利用できないっぽいです…

では、Javaで同じことをするには…?ということで考えてみましょう。

Javaには関数、というよりメソッドを直接渡すようなことはできません。ああ、通常の構文として、という意味で、リフレクションを使えばMethodを渡せるじゃんとかいうのはここでは置いておきます。
※それではタイプセーフではなくなるので、Javaでやっている意味がなくなります

まずは、関数を表すインターフェースを定義します。

public interface Function1<IN, OUT> {
    public OUT apply(IN in);
}

1って何だ?と思うかもしれませんが、引数を1つ取る関数の意味です…。2つ取るなら、Function2とか付けるんでしょうね。

続いて、foreachメソッドとmapメソッドを持ったクラスを宣言します。
※簡単のため、ArrayListで決め打ちにしています

import java.util.*;

public class FunctionalCollections {
    public static <T> void foreach(List<T> list, Function1<? super T, Void> func) {
        for (T t : list) {
            func.apply(t);
        }
    }

    public static <T, S> List<S> map(List<T> list, Function1<? super T, ? extends S> func) {
        List<S> newList = new ArrayList<S>();

        for (T t : list) {
            newList.add(func.apply(t));
        }

        return newList;
    }
}

なお、引数になっているFunction1の下限境界ワイルドカード、上限境界ワイルドカードは、getとputの原則に則っています。

Java の理論と実践: Generics のワイルドカードを使いこなす、第 2 回
http://www.ibm.com/developerworks/jp/java/library/j-jtp07018.html

続いて、foreachとmapの引数となるクラスを宣言しましょう。こちらは、簡単のためIntegerに決め打ちにしました。

class Triple implements Function1<Integer, Integer> {
    @Override
    public Integer apply(Integer in) {
        return in * 3;
    }
}

class PrintElem implements Function1<Integer, Void> {
    @Override
    public Void apply(Integer in) {
        System.out.print(in);
        return null;
    }
}

今回は戻り値もIntegerですが、別にStringとかに変換しても問題ありません。あと、ジェネリクスとvoidって相性悪いですよねー。

これを使う側のコードは、こうなります。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

FunctionalCollections.foreach(list, new PrintElem());  // -> 12345
System.out.println(FunctionalCollections.map(list, new Triple()));  // -> [3, 6, 9, 12, 15]

Pythonのlambdaもどきのことをしようとすると、こうなります。

FunctionalCollections.foreach(list, new Function1<Integer, Void>() {
        @Override
        public Void apply(Integer in) {
            System.out.print(in);
            return null;
        }
    });  // -> 12345

System.out.println(FunctionalCollections.map(list, new Function1<Integer, Integer>() {
            @Override
            public Integer apply(Integer in) {
                return in * 3;
            }
        }));  // -> [3, 6, 9, 12, 15]

…長い!!

つまり、Javaは引数の受渡しには必ずクラスのインスタンスを要求するため、こういう記述になってしまいます。やりたいことは

System.out.print(in);

と

return in * 3;

のはずなのに、そこに至るまでの過程が長い長い。もっとサックリ書けたらいいのに、と思いません?ぶっちゃけ、処理よりもクラスの宣言の方が長いんですけど…。

両者の差は、関数を一人前の値(オブジェクト)として扱えるかどうか、という点に帰結します。

ただ、別にJavaをバカにしたいわけではありません。上記PythonとJavaのコードは本質的に同じですし、Javaではこういう構造が表現できないわけでもありません。問題なのは、シンタックスが重い、それだけなのです。

よって、Javaではこういう発想をしているコードはあまりなく、「リストを反復処理しなさい」と言われれば、その場でforループやIteratorで回すか、リストそのものをメソッドの引数として渡し、メソッド内でループを利用します。つまり、

for (T element : list) {
   // 何か処理
}

のような決まりきったコードが、リストが登場する度に散在することになるわけです。この構造を一般化して、「// 何か処理」の部分だけを外から渡すことができると、処理が共通化できると思いませんか?

これができると、「リストをforループで回す」とか「リストの各要素に処理を行う」とか「リストに含まれる要素の合計値を求める」とかいうループなどの実装の詳細が隠ぺいされ、より抽象度の高い宣言的なコードが書けるようになります。他にもテンプレート的なコードでいくと、リソースのクローズのような処理がありますね。何の疑問も無く毎回try〜catch〜finallyを書いていると思いますが、try〜catchの中を外から渡し、使ったリソースは自動的に閉じるようにすればいいんですよね?

これは、身近な言葉で言うと超汎用的なコマンドパターンに他ならないのですが、Javaでこのアプローチをやってしまうと粒度の小さいクラスの増加を招きます。また先述の通りシンタックス的にも重くなりがちなため、あまり一般的ではないかと思います。

ただ、これを知ると発想が広がるので面白いですよ。初めて理解した時は、目から鱗が落ちる思いでした。

たまには、Java以外の言語で遊んでみましょう♪直接Javaで表現することは難しいかもしれませんが、プログラミングの引き出しを増やすためのいい刺激になるかと思いますよ。