Java8で体験するオブジェクトと関数の狭間

Java8でlambda構文が導入されることで、Java言語も関数型のような性質をもつことになりました。
関数型の性質として大事なことのひとつに、関数を戻り値として返せるということがあります。lambda構文によって、Javaでも表記上は関数を戻り値として返すことが可能になったわけです。
で、関数を戻り値として返せるとどうなるかというと、関数をオブジェクトのように使えるようになります。まあ、Javaでは関数といっても普通のオブジェクトとして扱われるので、関数としてオブジェクトのようなものが記述できる、ということになります。


では実際に、オブジェクトのようなものを関数として記述してみます。

    public static Function<String, Object> myFunc(String name, String address, LocalDate birthday){
        return prop -> {
            switch(prop){
                case "name": return name;
                case "address": return address;
                case "birthday": return birthday;
                case "introduce": return (Runnable)() -> {
                    System.out.printf("わたしは%s。%sに住んでて%tY年%<tm月%<td日生まれ%n", 
                            name, address, birthday);
                }
                case "age": return (Function<LocalDate, Integer>)d ->
                        birthday.periodUntil(d).getYears();
            }
            return null;
        }
    }


ここでは、文字列でname、address、日付でbirthdayを受け取ると、「文字列をうけとってオブジェクトを返す関数」を返すような関数を定義しています。
ちなみに、NetBeansのデイリービルド201305152300版では、コンパイルエラーと表示されます。
※ returnのあとのセミコロンがなくてもb88でコンパイル通るというのが原因ぽい。これは仕様?
※2 b89だとコンパイルエラーになるらしい。残念。セミコロン追加してください。


このmyFuncをこんな感じで呼び出しますね。

Function<String, Object> func = myFunc("きしだ", "ふくおか", LocalDate.of(2007, 5, 26));
System.out.println(func.apply("name"));
System.out.println(func.apply("address"));
System.out.println(func.apply("birthday"));


表示はこうなります。

きしだ
ふくおか
2007-05-26


もどってきた「関数」が、name・address・birthdayといった属性をもっているようにふるまっています。myFuncはコンストラクタのような役割になっていますね。
定義では、文字列「name」を受け取ったらmyFuncの第一引数を返すといった「関数」をlambda構文で記述していて、クラスやオブジェクトを定義・生成するような構文は使っていません。


メソッドはどうでしょう?「introduce」という引数で「関数」を呼び出すと、Runnableオブジェクトが返ってきます。これは引数をもたず戻り値もない処理をあらわします。

Runnable introduceProc = (Runnable)func.apply("introduce");
introduceProc.run();


これを呼び出すと、次のように表示されます。

わたしはきしだ。ふくおかに住んでて2007年05月26日生まれ

ちゃんと、myFuncに渡した値が使われていますね。


引数をもつメソッドも定義できています。日付を渡すと、その時点での年齢を返してくれます。

Function<LocalDate, Integer> ageFunc = (Function<LocalDate, Integer>)func.apply("age");
System.out.println(ageFunc.apply(LocalDate.now()) + "歳");


次のように、正しく表示されますね。

5歳


このように、myFuncを呼び出して返ってきた「関数」が、オブジェクトのように使えることがわかりました。


このような、関数をオブジェクトのように扱う記述は、関数を値として扱えるような言語では比較的自然に記述することができるのですが、Javaではlambda構文で関数の記述はできるようになったものの値としてはオブジェクトとして扱われるので、非常にイビツな記述になっています。
コードの説明も、やはりちょっと苦しいです。


ただ、これが、なんだかオブジェクトと関数の狭間に迷い込んだことをあらわしていて非常に面白いなと思いました。
ちなみに、2007年5月26日は、Twitterをはじめた日なので、誕生日として扱っても間違いないです。


全体は次のようになります。

import java.time.LocalDate;
import java.util.function.Function;

public class FuncObj {
    public static void main(String... args){
        Function<String, Object> func = myFunc("きしだ", "ふくおか", LocalDate.of(2007, 5, 26));
        System.out.println(func.apply("name"));
        System.out.println(func.apply("address"));
        System.out.println(func.apply("birthday"));
        
        Runnable introduceFunc = (Runnable)func.apply("introduce");
        (introduceFunc).run();
        
        Function<LocalDate, Integer> ageFunc = (Function<LocalDate, Integer>)func.apply("age");
        System.out.println(ageFunc.apply(LocalDate.now()) + "歳");
    }
    public static Function<String, Object> myFunc(String name, String address, LocalDate birthday){
        return prop -> {
            switch(prop){
                case "name": return name;
                case "address": return address;
                case "birthday": return birthday;
                case "introduce": return (Runnable)() -> {
                    System.out.printf("わたしは%s。%sに住んでて%tY年%<tm月%<td日生まれ%n", 
                            name, address, birthday);
                }
                case "age": return (Function<LocalDate, Integer>)d ->
                        birthday.periodUntil(d).getYears();
            }
            return null;
        }
    }
}