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で衚珟するこずは難しいかもしれたせんが、プログラミングの匕き出しを増やすためのいい刺激になるかず思いたすよ。