BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース ラムダに関する最新情報

ラムダに関する最新情報

原文(投稿日:2010/10/19)へのリンク

OpenJDK の今後に関する事態も落ち着きを見せ,PlanB によってラムダの JDK 8 (あるいはそれ以降) への先延ばしが確定した今,ラムダ自体は今後どのようになるのだろうか?

ラムダに関する最初の提案 は,Java 言語への関数型(functional type) の導入なども含んだ,かなり広範囲なものだった。しかし Java の型システムとの接点に根本的な問題が存在したため,新たな関数型 (あるいは構造的型付け/structural typing) の提案は撤回され,既存のクラスベース (あるいは名義的型付け/nominal typing) の方法が支持される結果に落ち着いた。ある意味これは,XObject のサブタイプならば array[X]array[Object] のサブタイプである,という事実が原因である。例外をスローする関数と組み合わせた場合,Java の型システムを損なう可能性があるのだ。

それ以降のラムダに関する提案では,クラスの匿名インスタンスの提供が話題の中心とされてきた。実質的にこれは,匿名内部クラスで使用される定型的なコードの置き換えとなるものだ。最も 新しい提案 によると,ラムダ はそれぞれ SAM 型と呼ばれる型のインスタンスになる。SAM 型 (SAM Type - Single Abstract Method Type) とは,抽象メソッドをひとつだけ所有するインターフェースあるいは抽象クラスである。既存の内部クラスの多くは暗黙的に SAM 的アプローチに従っている (RunnableComparable など) ことから,これはラムダの適用が予想されるユースケースに幅広く適応する方法だ。

最新のドラフトでは,ラムダからの復帰を指示する独立したキーワード yield の使用という,提案に対して異論の多かった構文要素のひとつに対処している。(この目的に return を採用しなかった当初の理論的根拠は,ラムダを含むメソッドからの復帰を可能にすることにあった。しかし一方では,既存のコードをラップしてインライン修正に置き換える "透過的ラムダ" を望む声がある。いずれにしても Java では,Smalltalk のようなブロック表現やコントロール構造が実現できないので,完全な透過性が実現できるとは考えにくい。)

もうひとつの変更は,ラムダ引数の構文に関するものだ。以前のドラフトでは '平方' を求めるラムダを #(int a) { a*a } と記述していた。現行の案ではこれを #{a->a*a} という,Java のメソッド構文とは少し違うが,よりコンパクトな形式に置き換えている。コンパイラがこの式から型を推論できる場合 (たとえば SAM 型へのアサインや,既知の型の引数を持ったメソッドの明示的な呼び出しなど) については不要だが,そうでなければ (上の例であれば,aintfloat その他の場合がある) #{int a -> a*a} のような型指定が必要である。

他の関数型言語に比較すれば初歩的ではあるものの,型推論の追加は,ラムダ式の記述に必要な文字数を相当に削減する。動的型付け言語の支持者であれば,静的コンパイル言語におけるこれらの機能を,間違いなく有益なものと考えるだろう。ただし,コンパイル言語であっても (Scala のように) さらに強力な推論機構を持つものがあることに留意すべきだ。Javaの型推論がシステムの他の部分に拡張されるかどうかは未定だが,Project Coin では代入での一般型推論をすでにサポートしている。

さらに異論があるのは,ラムダにおける this の意味が以前のドラフトとは逆転してることだ。これまでは内部クラスにおける解決法と同じように, this がラムダのインスタンスを,Outer.this がそれを包含するクラスを参照するものだった。現在では this は包含クラスを示して,ラムダそれ自身を参照する正式な方法は失われている。ドラフトでは代入時点での自己参照を行う可変定義を導入して,これに対処しようとしている。言い換えれば,現在の Java コードでは無効な int a = a + 1; というような記述を可能にしようというのだ。ドラフトでの例は次のようなものだ。

Timer timer = ...
final TimerTask t = #{ 
    if (somethingHappened())
        // cancel the timer task that this lambda represents
        t.cancel();
    else
        System.out.println("foo");
});
timer.schedule(t);

ここでの問題は #{..} の型が匿名の MethodHandle という,実質的に型の概念を持たないものである点だ。その代わり,これがローカル変数の型に割り当てられている場合は SAM の事実上のサブタイプに昇格される。そのためメソッドのハンドルである #{..} に対して,それを包含する (SAM の) ラッパが作成される。これによって自分自身を再帰的に起動したり,SAM 型の他メソッドを呼び出すことが可能になる。ここでのテーマは,メソッドハンドルが自身のタイプを暗黙的に知ることができない (それはメソッドが参照される場所で,コンパイラが引数の型を推測することによって初めて分かる) ため,サブクラスのインスタンスからの再帰呼び出しの実装や,フィールドあるいはメソッドへのアクセスには使用できないことだ。

現時点でドラフトは十分に成熟していて,実装の進捗も良好である。JDK 7 には間に合わなかったものの,JDK 8 で採用された場合に重大な影響を起こすような問題はない。拡張メソッド (別名 defender method) と例外処理に関する既存動作に変更はない。実際にただひとつ存在する未解決の問題は,MethodHandleSerializable か,そうでないなら code>Serializable なオブジェクトに設定された場合の振る舞いはどうか,ということだ。

この最新の進捗状況について,読者はどのような感想をお持ちだろうか?

この記事に星をつける

おすすめ度
スタイル

BT