BT

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

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース Java 10でラムダが強化される可能性あり

Java 10でラムダが強化される可能性あり

ブックマーク

原文(投稿日:2017/01/17)へのリンク

新しいJEPには、より明確な曖昧さ回避、未使用パラメータのアンダースコアの使用、外部変数のシャドウイングなど、ラムダ機能を強化するための変更提案が提出されている。これらの変更によって、Javaのラムダが他の言語のラムダに近づくことになるが、最初の議論では支持するレベルも様々であった。このJEPは、Java言語を改善するための、他の一連の提案を補完しており、ローカル変数型の推論拡張された列挙型を含む。これらは全てJava 10に含まれる可能性がある。

3つの変更はすべてラムダに関連しているが、それらは独立しており、フィードバックに応じて、一部が不採用となり、他のものが採用される可能性がある。そのため、この記事では個別に説明する。

より明確な曖昧性回避

ラムダがバージョン8でJavaに追加されたとき、それらをサポートするために型推論を修正しなければならなかった。しかし、過去に行われた変更は、サポートできるほどには至らなかった。その理由の一部は、それらの変更がラムダに初めて触る開発者を混乱させる懸念があるためである。しかし、この点については状況が変化しているようで、開発者の中には、推論に十分な情報がコンテキストによって提供されているにも関わらず、コンパイラがラムダの型推測に失敗することに不満を感じる人もいる。次の例は、現時点において、ラムダの型推論がどのように動作しているかを示している。

// Case 1: type of lambda inferred as Predicate<String>
//         first overloaded version of method will be called.
private void m(Predicate<String> ps) { /* ... */ }
private void m(Function<String, String> fss) { /* ... */ }

private void callingM() {
    m((String s) -> s.isEmpty()); 
}

// Case 2: not enough information to infer type of lambda independently,
//         but m2 isn't overloaded.
//         Signature of method argument is applied to lambda to infer
//         that s is String and that s.length() returns Integer
private void m2(Function<String, Integer> fsi) { /* ... */ }
private void callingM2() {
    m2(s -> s.length());
}

// Case 3: not enough information to infer type of lambda independently.
//         m3 is overloaded, but different versions have different arity,
//         only first version has matching number of parameters (1).
//         Signature of method argument is applied to lambda to infer
//         that s is String and that s.length() returns Integer
private void m3(Function<String, Integer> fsi) { /* ... */ }
private void m3(Function<String, Integer> fsi, String s) { /* ... */ }

private void callingM3() {
    m3(s -> s.length());
}

// Case 4: not enough information to infer type of lambda independently.
//         Multiple overloaded versions of m4 with same arity available.
//         Ambiguous call, ERROR
private void m4(Predicate<String> ps)  { /* ... */ }
private void m4(Function<String, String> fss)  { /* ... */ }

private void callingM4() {
    m4(s -> s.isEmpty());
}

しかし、この最後のケースによって、コンパイラは現在その情報を使用していないが、m4の最初のオーバーロードされたバージョンを使用すべきと結論付けられる。新しい提案では、コンパイラは曖昧さを回避するために次の手順を実行する。

  1. ラムダの引数の型について、2つの可能性があり、そのどちらもStringであるため、sString
  2. sStringであることが分かったため、String.isEmpty()メソッドはbooleanを返す
  3. ラムダはbooleanを返すため、m4の2番目のバリエーションは一致しないため、それは破棄される
  4. 唯一残った選択肢はm4の最初のバリエーションであり、推論されたラムダの型と一致するため、これが使われる

メソッド参照にも同様の議論がある。

未使用パラメータに対するアンダースコアの使用

ラムダが多くのパラメータを持つようなシナリオがあるが、ボディがそれらすべてを使用するわけではない。そのため、開発者は未使用のパラメータであることを示す名前を使わなければならない。この変更により、そのような未使用のパラメータにアンダースコアを使用できるようになる。

Function<String, Integer> noneByDefault = notUsed -> 0; // currently
Function<String, Integer> noneByDefault = _ -> 0; // proposed

これはScala、Ruby、Prologなどの他の言語でも利用可能な機能であるが、Javaでは簡単には実装できなかった。それは、Java 7まではアンダースコアはコード内で使うことができる有効な識別子であったためである。大量のコードの書き換えを行わずにこの変更を導入するには、ゆっくりとした一連のステップが必要であった。

  1. Java 8:アンダースコアが識別子として使用されると警告が発行され、開発者がそのように使うことは非推奨となる。アンダースコアはラムダの識別子として許可されていない(ラムダはJava 8より前には利用できなかったため、後方互換性が損なわれることはない)。
  2. Java 9:以前の警告がエラーに変わり、識別子としてのアンダースコアの使用があらゆるJavaコードから削除されることが保証される。
  3. Java 10(またはそれ以降):アンダースコアは識別子として再導入されるが、ラムダ式の未使用のパラメータに対してのみである。

この変更に対する支持は初期の対話から判断すると、全会一致ではい。一部のユーザーは新しい提案の簡潔さを好むが、一部のユーザーは明示的な名前の使用を好む。さらなる議論により、コンセンサスが得られるであろう。

パラメータのシャドーイング

おそらく、新しく提案された機能の中で最も議論が多いものである。現時点では、ラムダパラメータは外部変数をシャドーすることができない。つまり、現在のスコープでアクセス可能な変数とは異なる名前を選択する必要がある。これは、whileループやifステートメントのような他の囲みスコープの動作に合わせている。

String s = "hello";

if(finished) {
    String s = "bye"; // error, s already defined
}

Predicate<String> ps = s -> s.isEmpty(); // error, s already defined

提案された変更が進めば、ラムダパラメータは既存の識別子を再利用してシャドーすることができる。これはラムダパラメータに、より直接的でない他の名前を使用する必要がないケースに対してはメリットがある(上の例では、通常s2 -> s2.isEmpty()に書き換えられる)。しかし、有名な国際的スピーカーであるRoy Van Rijn氏が示しているような巧妙なバグを引き起こす可能性がある。

Map<String, Integer> map = /* ... */
String key = "theInitialKey";

map.computeIfAbsent(key, _ -> {
   String key = "theShadowKey"; // shadow variable
   return key.length();
});

現在、上記コードは許可されていないが、新しい提案では可能である。「shadow variable」とマークされた行が削除されても、コードをコンパイルして実行できるが、全く異なる処理を行う。

上記をJavaに導入するかどうか、また、どのような形で導入するかを評価するために、多くの対話がまだ必要である。しかし、明らかなのは、Java 8へのラムダ導入は、Java言語の多くの将来の改良の第一歩でしかないことである。

 
 
 

Rate this Article

Relevance
Style
 
 

この記事に星をつける

おすすめ度
スタイル

BT