Java : Closures for Java

前のエントリーでは、元ネタを確認してなかったので、チェックしてみました。といっても、原文ではなくって、co-authorによるサマリですが。
http://gafter.blogspot.com/2006/08/closures-for-java.html

クロージャの定義

ここで議論されているのは "function types and inline function-valued expression, called closures" ということらしいです。

function type
組み込み関数型のことです。関数型言語のそれとは違うのに注意(関数型言語は "functional language" ですから、関数的言語といった方が混乱しないかもね)
closure
"inline function-valued expression" とか、後の方で、"Anonymous Functions (Closures)" といっているので、いわゆる「無名関数」のことをクロージャと呼んでいるみたいです。それは誤用だと思うのですが、もうその使い方が市民権を得てしまった以上は、そういうものだと理解するしかないのでしょうかね。誰かがクロージャと言っていたら、何を指しているかは相手を見て判断しましょうということで。

関数型

は次のように記述するみたいです。

int(int, Object, String) throws IOException | InterruptedException

throws節の区切りが '|' を変更するそうです。というのは、

int(int() throws IOException | InterruptedException , void())

のように、関数型をカンマで区切って記述するのに throws節がカンマ区切りだと混乱するからです。
この変更はメソッド宣言の方にも適用されるのですが、メソッド宣言は過去の互換のためにカンマ区切りも存続させるということだそうです。

ローカル関数

を宣言できるようになります。また、関数型で宣言した変数にローカル関数を代入することができます。

void method() {
  int add(int a, int b) { return a + b;}
  int(int,int) plus = add;
  return plus(1, 2);
}

ローカル関数にもアノテーションを付けることはできるそうです。

Javaでは、メソッドの名前空間と変数の名前空間は別なのですが、ローカル関数は変数の名前空間に属します。また、関数型の変数が存在することで、メソッド呼び出し(関数呼び出し)があった場合に、メソッドの名前空間と変数の名前空間のどちらを優先すべきかという問題が生まれますが、メソッドの名前空間を優先します。それから、ローカル関数は変数なので、オーバーロードはできません。(原文にはambiguousだからオーバーロードしないと書いてあるのですが、変数ならambiguousも何もないような気がしますが)

ところで、メソッドを関数型変数に代入することはできるのでしょうか?その場合は、名前空間の探索順序はどうなるのでしょうか?

あと、メソッドや関数の返値に関数型があった場合は、そのまま続けて関数呼び出しできます。

int(int) add(int x) { return int(int y) : x + y;}
return add(1)(2)

無名関数(クロージャ

上の例で使ってしまいましたが、無名関数は次の2通りの記法になります。

(int x) { return 2*x;}
(int x) : 2*x;

型付きラムダ式とよく似た記法ですね。引数の型だけ指定して、返値の型は指定していませんが、これは推論されます。

必ず例外が投げられる場合
返値はnull型になります。
returnがなく、例外が投げられることなく終了することがある場合
返値はvoid型になります。
returnがある場合
JLS3 15.25のルールを援用して、複数のreturnの式の型を統合した型が、返値の型になります。

これに伴って、これまでは明示的に記述できなかったnull型が、nullという予約語で表現できるようになります。

サブタイプ

基本的には、返値と例外については covariant で、引数については contravariantです。

ただし、サブタイプ関係が通常の文脈とは少し違っています。明示的には書いていなかったですが、void型は全ての型のスーパータイプで、基本型はvoid以外にスーパータイプを持たないと考えれば、よいのかな? null型の扱いについては記述がなかったようですが、すべての型のサブタイプであるべきなのではないかと思います。

リフレクション

関数型に対するリフレクションは、ClassにinvokeFunction()メソッドが追加され、FunctionTypeクラスが追加されるそうです。

なんとなく、invokeFunction()メソッドは、FunctionTypeクラスのメソッドであるはずなのではないかと思ったのですが。

また、invokeFunction()はSecurityManagerによるアクセスコントロールの対象外になるみたいです。

囲む環境のローカル変数へのアクセス

これまでは final指定が必須だったのですが、その制限を無くすそうです。

これについては、concurrencyに関するところの議論がされていますが、それよりも、無名関数や内部クラスからアクセスされるローカル変数はスタック領域ではなくヒープ領域に確保する必要があると思うのですが、それとガーベジコレクションとの関係との方が興味があります。

Non-local transfer

日本語でなんと表現すればよいのか分からないですが、break, continue, returnを使って、ローカル関数の外の環境に脱出することです。

void invoke_if_even(int i, void() f) {
  if (i%2 == 0) { f();}
}

int i=0;
while (true) {
  invoke_if_even(i++, (){ continue;});
  System.out.println(i);
}

みたいに書くと、continueと書いたところが実行されると、その後の println()が実行されないで whileの次の実行に移るということです。みずしまさんがどうやって実装するのだろうと疑問を投げていたのはこれですね。

breakとcontinueはそのまま見た目通りに使えます。定義したところのループに対してジャンプするので、ループの外側で関数が実行されると実行時エラーになります。

returnは、外側のメソッド名を付けて呼ぶ必要があります。こんな感じで。

void outermethod() {
  void f() { return outermethod:;}
  System.out.println("abcde");
  f();
  System.out.println("fghij");
}

囲むのがメソッドでなくてローカル関数でも同じなんでしょうか? あと、ネストしたローカル関数から、何段も飛ばしてreturnできたりするのでしょうか?

無名関数からインターフェースへの変換

1メソッドインターフェースを引数に要求しているところに、無名関数を与えることができるようにするということです。つまり、

void doit(Runnable f) {
  f.run();
}

のようなメソッドに、

doit((){ System.out.println("abcde");});

のような呼び出しができると、既存のライブラリを変更しなくても、無名関数の恩恵が受けられるということです。ついでに、返値に対する基本型のオートボクシングも同時に有効にするそうです。

実装方法については、どうすればよいかまだ検討中みたいで、いくつかの案が書かれています。その中身はまあ、いいのですが、それよりも

This may be nearly good enough from the point of view of how concise the usage is, but it has one more serious drawback: every creation of a Runnable this way requires that two objects be allocated instead of one (one for the closure and one for the Runnable), and every invocation of a method constructed this way requires an extra invocation. For some applications -- for example, micro-concurrency -- this overhead may be too high to allow the use of the closure syntax with existing APIs. Moreover, the VM-level optimizations required to generate adequate code for this kind of construct are difficult and unlikely to be widely implemented soon.

とあって、メソッド呼び出しが2重になるのが実行時のオーバーヘッドになることについて記述されているのを見て、処理系を考える場合はそういうことは考えるよな、とか思いました。

あと、ここでは議論されていなかったですが、ローカル関数として他で宣言されているものについても、同様の変換がサポートされるのでしょうか?

さらに突っ込んだアイデア

メソッドの引数の最後がゼロ引数の関数型であった場合、

foo(a1, .., an, () {...});

と書く代わりに

foo(a1, .., an){...};

と書けるようにすると、ユーザー定義制御構文が作れるから面白いね、という話。これって、Rubyのマネですか?

ただし、これだと、無名関数内にreturn文を含んでいると、直感に反する挙動になる(囲むメソッドからreturnするのではなく、無名関数がreturnするだけ)ので、無名関数内のreturn文の構文を変更したらいいかもね、ということで、

foo(a1, .., an){
  if (i>0) {
    ^ "return value"; // 無名関数から returnする
  }else{
    return i; // 外側のメソッドから returnする
  }
}

というように書くのを提案しています。

・・・って、これはさすがに ad-hoc な気がしますが。

感想

クロージャの用語法に引っかかりは感じるものの、それ以外については、想像していたよりいろいろあって、面白そうな気がしました。クロージャの導入というより、関数型の導入という方が正しい認識だと思いました。

これまでのJavaがメソッドだけだったところに、新たに関数というものが導入されると、メソッドと関数の間で概念的な曖昧さが生まれて、そこに数々のバッドノウハウがたまりそうな気がしなくもないですが、なるべく整合的に仕様策定して欲しいと思います。