ProjectLambda・不定期レポート(2012/7)
- void<->Void変換
- メソッドジェネリクスを使用したラムダ式
- 匿名クラス等からの実質的にfinalな変数への参照
- 再帰ラムダ式
- ターゲット型による型引数の推論の拡張
- 新ライブラリIteration2
- デフォルト実装の再abstract化
- Objectのpublicメソッドへのデフォルト実装
ProjectLambdaについて二月ごろにちょこちょこ書いていましたが、約半年でかなり仕様などが変わっています。今回はそういった差分についてまとめておきます。
void<->Void変換
以前はvoid<->Void変換があったので次のように汎用的な関数型インターフェースを用いてvoidのメソッドを呼び出すラムダ式を書くことが出来ました。
void m() {} F0<Void> f = () -> m();
ですが、void<->Void変換がサポートされなくなってしまったのでこのように書くことが出来なくなりました。そういったことをしたい場合はブロック形ラムダ式を用いて
F0<Void> f = () -> {m();return null;};
と書く必要があります。
void<->Void変換が行われないようなラムダ式は従来どうりに書くことが出来ます。
interface Fv { void m(); } interface FV { Void m(); } void v() {} Void V() {return null;} Fv fv = () -> v(); FV fV = () -> V();
一時はvoid/Voidを返すようなラムダ式を式形を用いて書くことすらできなくなっていました。
さすがにかなり面倒くさくなるのでこのような形に落ち着きましたww
注:まだbuild48ではvoid/Voidを返すラムダ式を式形で書くことはできない状態です。次のビルドでは上のように可能になっていると思います。
メソッドジェネリクスを使用したラムダ式
以前
最後に残ったメソッドジェネリクスですが、2012/02現在未実装な上、特に普通のメソッドジェネリクスと変わり無いように思えるので説明は避けさせてもらいます。
イメージとしては次のようになります。<T, U extends Clazz> (t, u) -> new Pair<T, U>(t, u)
と書きましたが、メソッドジェネリクスを使用したラムダ式はサポートされない事が概ね決定しました。
というのも、メソッドジェネリクスを用いてラムダ式を書けるようにするととんでもなく難解な式が出来上がってしまう可能性があるのです。
例えば次のような物です。
foo( (x) < y , z > (w) -> v );
もしこのような式が出てきてfooの引数部分がどのような物か容易に判断が付くでしょうか?
この式を分かりやすく書くと次のようになります。
foo( (Functional) <T, S> (arg) -> val );
この引数はキャスト式がターゲット型を与え、そのターゲット型へのメソッドジェネリクスを有したラムダ式です。
この様に理解しづらい構文でメソッドジェネリクスを導入するのはあまり得策ではないでしょう。
匿名クラス等からの実質的にfinalな変数への参照
ラムダ式内から実質的にfinalな変数への参照はサポートされていましたが、匿名クラスなどからの参照はサポートされていませんでした。
ですが、これがサポートされ、次のようなことが出来るようになりました。
void m(int m) { add( new Printer() { public void print(PrintStream ps) { ps.print(m); } }); }
メソッド内の匿名クラスなどが地味に便利(?)になりました。
再帰ラムダ式
匿名クラスなどからの実質的にfinalな変数への参照のサポートの際の実装の都合上ローカル変数を用いた次のような再帰ラムダ式が書けなくなってしまっています。
void m() { F f = () -> f.invoke(); }
なお、ローカル変数を用いたものだけがダメなのでフィールドを用いれば問題なく書くことが出来ます。
F f; void m() { f = () -> f.invoke(); }
ローカル変数の再帰ラムダ式のサポートの除去は一時的な物だとは思います。再度サポートされるまでは、メソッド内で使う再帰ラムダ式のために外の空間を汚すのは気に食わないですが、フィールドを用いて書く必要があります。
ターゲット型による型引数の推論の拡張
ターゲット型による型引数の型推論が少しだけサポートされました。
現在サポートされているのは極めて簡単な物だけです。
List<String> addString(List<String> list, String s) { list.add(s); return list; } List<String> a = addString(new ArrayList<>(), "FirstElement"); // OK(Java7ではNG) <T> List<T> emptyList() { return new ArrayList<>(); } List<String> b = addString(emptyList(), "FirstElement"); // OK(Java7ではNG) List<String> c = addString(this.<String>emptyList(), "FirstElement"); // Java7
addStringの引数List
このaddStringをメソッドジェネリクスを用いて明示的に型引数を指定しない場合は型推論が働きません。
<T> List<T> add(List<T> list, T t) { list.add(t); return list; } List<String> a = add(new ArrayList<>(), "FirstElement"); // NG List<String> b = add(emptyList(), "FirstElement"); // NG
まだ戻り値や他の引数からターゲット型を推論することはできないみたいです。更なる型推論の強化がされることを期待します。
なお、addのTに対して明確に型を与えてあげれば型推論を使用することができます。
List<String> a = this.<String>add(new ArrayList<>(), "FirstElement"); // OK List<String> b = this.<String>add(emptyList(), "FirstElement"); // OK
新ライブラリIteration2
現在、Iterableにデフォルトメソッドを追加する形でラムダ式を用いたコレクション操作が出来るようになっています。そのライブラリをIteration 1と呼びます。
そしてIteration 1を拡張した形のライブラリ群Iteration 2の開発が現在されています。まだ明確な仕様が決まっていないのでどのようになるか分かりませんが、Iteration 1よりも便利になったり、ラムダ式を用いたコレクション操作がMapに対しても直接出来るようになると思います。
デフォルト実装の再abstract化
デフォルト実装を解除する場合はabstractメソッドとして再宣言する必要がありました。2012/3の時点では実装がされていませんでしたが、実装されています。
interface I { void m() default {} } interface I2 extends I { void m(); // 再abstract化 } interface I3 extends I { abstract void m(); // 再abstract化 }
Objectのpublicメソッドへのデフォルト実装
注:まだbuild48にはまだ反映されていません。
Objectで宣言されたpublicなメソッドに対してデフォルトの実装を与えることはできません。
interface I { String toString() default {return "this is default";} // NG Object clone() default {return this;} // OK(cloneはObject内でpublicでないため) }
Objectのpublicメソッドへのデフォルト実装を許すと混乱を招く恐れがあるためです。