2012年12月9日日曜日

Xtend の Lambda とストリーム処理


この記事は、Java Advent Calendar 2012 の第9日目です。
前の記事は すふぃあ氏(@empressia)の記事 です。
明日の記事は 谷本 心氏(@cero_t) です。

Java に Lambda が来る!

Java 8 に Lambda が入ることはまことに喜ばしい限りですね。でも、使えるのはまだちょっと先のことになりますし、Java 8でホントに入るの?という心配もあります (^_^A;
Java 8 が出ても、すぐには使えない仕事も多いですしね。Java プロジェクトで実際に Lambda を使うのは、しばらく先のことになってしまうかもしれません...。


そこで Xtend です!

Java プロジェクトで Xtend を使えば、今すぐに Lambda を使えます!
Xtend については、以前書いた記事(社内向けのXtend紹介資料) を参考にしてもらうことにして、今日は Xtend の Lambda とストリーム処理のシンプルさについて、ご紹介しようと思います。

Xtend の Lambda の基本

Xtend の Lambda は、Java のそれと同じく、単一メソッドインタフェースに対するシンタクスシュガーです。Xtend の Lambda は、Java と同じイディオムで使用でき、相互運用性もある点で便利です。
ただ、記法は異なります。

従来の Java の以下のような局面では、Lambda を使うことができます。

List<Person> men = ids.map(new Function1<ID, Person>() {
 @Override
 public Person apply(ID id) {
  return Person.get(id);
 }
}).filter(new Function1<Person, Boolean>() {
 @Override
 public Boolean apply(Person p) {
  return p.sex == MALE;
 }
}});

Java 8 の Lambda ならば、以下のように書くでしょう。

List<Person> men = people.map(id -> Person.get(id)).filter(p -> p.sex == MALE)

Xtend では以下のように書けます。

ポイントは、型推論と暗黙引数 it ですね。

val men = ids.map[id| Person::get(id)].filter[p| p.sex == MALE]
val men = ids.map[Person::get(it)].filter[sex == MALE]

Xtend の Lambda は、-> 記号ではなく、[]で囲んだ形になっています。
これは一見妙な感じもしますが、DSL 風の API を Lambda で作るには、結構向いているのです。

Xtend の関数型変数の記述

Xtend で関数型変数を表すシュガーは以下のようになりますが、型推論でより簡単に記述できます。

val (ID)=>Person mapper = [Person::get(it)]
val predicate = [Person p| p.sex == MALE]

Xtend の遅延コレクション評価は Stream 要らず

Java 8 では、遅延コレクション評価のために、Stream が追加されます。
曰く、無限データストリームを処理するのには遅延評価()が必要だが、外部イテレータでは記述が面倒になるので、遅延的な内部イテレータを提供する新しい仲介型が必要だ、ということです。Streamを返すように、既存のコレクション実装を修正すれば、皆が幸せになれると。

Xtend も、遅延コレクション評価の簡潔な記述を提供します。
しかし、Xtend が Java 8 と異なるのは、Stream のような新しい仲介型は導入せず、Iterable/Iterator をそのまま使える点です。
つまり、既存のコレクション(Iterable)も、改造無しにストリーム処理できるのです。

例えば、Xtend で以下のように記述すると、バルク処理ではなく、ストリーム処理になります。このことを少し詳細に見てみましょう。

ids.map[Person::get(it)].filter[sex == MALE].forEach[println(it)]

上記の式のチェーンのつなぎ目の型遷移は、以下のようになっています。

val Iterable<ID> ids = ...
val Iterable<Person> people = ids.map[Person::get(it)]
val Iterable<Person> men = people.filter[sex == MALE]
men.forEach[println(it)]

ここで生じる疑問は2点です。

  1. Iterable に map や filter というメソッドは無いはずでは?
  2. Iterable から Iterable への変換時にバルク処理になっているのでは?


1の答え。それは、Extension method です。

Xtend には Extension method があるので、Iterable に対して外からメソッドを追加したように記述できます。
具体的には、IterableExtensions によって、これらのメソッドが提供されています。


2の答え。それは、IterableからIterableを作るのに評価は不要ということです。

前段の Iterable を、後段の Iterable でラップして行くのです。
最終的な評価は、forEach の中で Iterator 経由で遅延的に行われることになります。これによって、ストリーム処理による遅延評価()が可能になっているわけですね。
ちなみに、この処理の実装として、Xtend の裏で Guava が使われています。

まとめ

Xtend を使うと、今日からすぐに Java プロジェクトに Lambda とストリーム処理を利用できます。

Xtend は、型推論や Extension method の導入によって、生の Java よりもプログラムを大幅に簡潔にしてくれます。
にもかかわらず、Xtend は Java の型システム、Java の思考方法を維持しているので、Java プログラマに大きな学習負担を強いることなく、実装効率を向上してくれます。
Java プログラマなら、1日勉強すると、Xtend をだいたい使いこなすことが出来ます。
Xtend は、Java ソースを生成するので、納品 Java プロジェクトにも使いやすいという特徴があります。

これらの特徴を持つ Xtend は、Java プログラマのための Better Java 言語として、とても魅力的ではないでしょうか。

ちなみに、私のチームでは、今では日常的に Xtend で Java プロジェクトを開発/納品しています。皆様も、ぜひ Xtend の Lambda お試しください。