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 お試しください。

2012年6月7日木曜日

XtendによるCSV集計サンプルコード

Xtendで、CSVファイルの内容をカラム毎に集計する、サンプルプログラムを書きました。会社の朝Xtend勉強会のお題で、その回答例として書いてみたものです。

package example
import com.google.common.io.Files
import java.io.File
import java.nio.charset.Charset
class CsvCalculator {
def static void main(String[] args) {
val total = new CsvCalculator().calculate('file.csv')
println(total)
}
def calculate(String file) {
readLines(file).map[s| s.toRecord].reduce[base, r| base + r]
}
def private readLines(String file) {
Files::readLines(new File(file), Charset::forName('utf-8'))
}
def private toRecord(String line) {
val ss = line.split(',')
new Record(Integer::valueOf(ss.get(0)), Long::valueOf(ss.get(1)))
}
}
@Data class Record {
int count
long volume
def operator_plus(Record r) {
new Record(count + r.count, volume + r.volume)
}
}

以下がポイントでしょうか。
  1. 行コレクションに対する map/reduce を、クロージャで書いている
  2. 行からRecordオブジェクトへの変換を、Extension methodで書いている
  3. Recordの足し込みを、"+"演算子オーバロードで書いている

ありきたりの内容ですが、Javaプログラマ向けの最初の練習問題としては、まぁまぁかな?

2012年1月20日金曜日

社内向けのXtend紹介資料

私の所属する会社には、毎月1回「帰社日」という、社員が集まって色々なセッションを行う日があります。私は今日そこで、Xtendを紹介しました。今後Xtendの機能を紹介するとっかかりにもなりますので、そのスライド資料をここに公開しておこうと思います。

ちなみに他のセッションとして、Xtendの強力なライバルでもあり先生でもある、Groovy紹介セッションもありました。Groovy紹介セッションの後に、私のXtend紹介セッションの流れだったので、むしろ短い時間でXtendの魅力を伝えることができた気もします。

2012年1月19日木曜日

JavaをよりパワフルにするXtend

EclipseのXtextチームが、Xtendという新しいJVM言語処理系をリリースしています。
Javaでソフトウェアを開発している人にとって、Xtendは導入の検討に値する言語処理系です。
沢山の言語やフレームワークがある現在、Javaよりも開発しやすい言語の選択肢は沢山ありますが、にも拘らずJavaを採用しているのには、他の言語を採用できないそれなりの理由があると思います。
Xtendは、Javaのソースを生成する言語処理系であり、Javaを置き換えるものではないため導入の障壁が低く、しかもJavaの多くのものをそのまま利用しているため、学習コストがとても低いというのが、魅力です。

Xtend == Java + α != 新言語
Xtendは独立した新言語というより、Java言語のエクステンションです。Xtendは、Javaプロジェクトの中で、Java言語と一緒に使います。
Xtendは、XtendクラスをJavaクラスのソースに変換します。
Xtendは、Javaのインタフェース、列挙、アノテーションなどは、そのままの形で利用します。

Xtendの考えは、Javaのインタフェースなどの要素は十分簡潔かつパワフルであり、複雑冗長になりがちなクラス実装記述さえ簡潔に置き換えれば、Javaの開発効率は大幅に改善できる、というものです。
このように、XtendはJavaのクラス実装しか置き換えないため、その習得は、新しい言語処理系をまるまる学習するよりも、はるかに容易です。
「Javaクラスの簡潔な書き方を覚える」という、とても軽い気持ちで学ぶことができるんです。

Hello Xtend !
Xtendを始めるのは、とっても簡単です。
Eclipseを用意すれば、以下の要領ですぐに始められます。
  1. Eclipse MarketplaceからXtendをインストールする
  2. Javaプロジェクトを作る
  3. Xtendクラスを新規作成
    srcフォルダにXtendクラスを作ります
    [File]/[New]/[Xtend Class] -> Name: HelloXtend
  4. Xtendライブラリをビルドパスに追加する
    Xtendエディタ上のビルドパスエラーを、Ctrl + 1でQuickFixします
  5. xtend-srcフォルダにJavaソースが生成されていることを確認
  6. Xtendクラスを編集する
  7. class HelloXtend {
    def static void main(String[] args) {
    println("Hello Xtend")
    }
    }
  8. Javaソースも生成されていることを確認
  9. HelloXtend.xtendをJavaアプリケーションとして実行
  10. 実行結果を確認

クロージャの例
Hello Xtendの例だけでは、Xtendの魅力が全く伝わらないですよね。
せっかくなので、Xtendのクロージャの例を見てみましょう。
コレクションのフィルタ処理が、簡潔に記述できることが分かると思います。

import java.util.List
import static extension java.util.Collections.*
class CollectionFilter {
def static void main(String[] _) {
val values = newArrayList(1, 5, 0, -2, -10, 4)
val filtered = positiveOnly(values)
println(filtered)
}
def static positiveOnly(List<Integer> values) {
values.filter(v| v > 0)
}
}


生成されるJavaソースの例
この時に生成されているJavaソースは以下のような感じです。
Xtendでのクラス実装がJavaソースにコンパイルされていることがわかります。
Xtendのトリックも、ちゃんとJavaコードになっています。

import java.util.ArrayList;
import java.util.List;
import org.eclipse.xtext.xbase.lib.CollectionLiterals;
import org.eclipse.xtext.xbase.lib.Functions.Function1;
import org.eclipse.xtext.xbase.lib.InputOutput;
import org.eclipse.xtext.xbase.lib.IntegerExtensions;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
@SuppressWarnings("all")
public class CollectionFilter {
public static void main(final String[] _) {
int _operator_minus = IntegerExtensions.operator_minus(2);
int _operator_minus_1 = IntegerExtensions.operator_minus(10);
ArrayList<Integer> _newArrayList = CollectionLiterals.<Integer>newArrayList(Integer.valueOf(1), Integer.valueOf(5), Integer.valueOf(0), Integer.valueOf(_operator_minus), Integer.valueOf(_operator_minus_1), Integer.valueOf(4));
final ArrayList<Integer> values = _newArrayList;
Iterable<Integer> _positiveOnly = CollectionFilter.positiveOnly(values);
final Iterable<Integer> filtered = _positiveOnly;
InputOutput.<Iterable<Integer>>println(filtered);
}
public static Iterable<Integer> positiveOnly(final List<Integer> values) {
final Function1<Integer,Boolean> _function = new Function1<Integer,Boolean>() {
public Boolean apply(final Integer v) {
boolean _operator_greaterThan = IntegerExtensions.operator_greaterThan((v).intValue(), 0);
return Boolean.valueOf(_operator_greaterThan);
}
};
Iterable<Integer> _filter = IterableExtensions.<Integer>filter(values, _function);
return _filter;
}
}

まだまだあるXtendの機能

Xtendには、クロージャの他にも、便利な機能がいくつもあります。
それらの機能が、クラス実装を簡潔にする手助けをしてくれます。
そしてそれらもまた、使うのはとても簡単なんです。
今度は、それらを紹介しようと思っています。