2011年9月18日日曜日

型クラスとEclipse Adapter

まことに恥ずかしながら、ScalaとHaskellの型クラス(Type Class)を初めて知りました。
きっかけは、@kmizuさんのプレゼン「言語アップデート -Scala編-」で、@eed3si9nさんのアーティクル「Scala Implicits: 型クラス、襲来」で学びました。
初めて知った型クラスではありましたが、日頃Eclipseアプリケーションっを作っていて、近いことをやっていることに気づきました。

Eclipse Adapterは型クラスに近い
型クラスを学んですぐに感じたのは、これはEclipseのAdapters Extension Pointを使ってやっていることに近い、ということです。

上記サイトの例から、型クラス導入のモチベーションを整理します。
  1. モデルをLabelMakerインタフェースに適合させたい
  2. モデルをラップする形でアダプターを実装するとアイデンティティ問題が起こる
  3. 如何にモデルのアイデンティティを維持しつつ、インタフェース適合させるか
  4. 型によるアダプタインスタンスの選択を組み込む
このような、インタフェース適合と型によるアダプタ選択は、Eclipse Adapterの仕組みを彷彿とします。

Eclipseにおける型によるインタフェース適合
Eclipseアプリケーションを作っていると、再利用部分と拡張部分を粗結合にするために、アダプターを使用することが多くなります。Eclipseでは、このような時にインタフェースを適合する方法として2種類の方法があります。
  1. モデルがIAdaptableを実装し、getAdapter()がアダプタを返すよう実装する
  2. Adapters Extension Pointを利用する
1のIAdaptableによる方法は、モデルにインタフェースの実装を強要するので、どのようなモデルにでも使える方法ではありません。もっぱら、適合元オブジェクトがWorkbenchPartのような場合に使います。
2のAdapters Extension Pointによる方法は、IAdaptableを実装しないモデルに対してもインタフェースを適合させられます。適合元と適合先の組み合わせによってアダプタを生成するAdapterFactoryを実装して拡張ポイントにコントリビュートします。


Eclipse Adapter使用例
Eclipse Adapterを使ったコード例は以下のようになります。

void printLabel(Address address) {
ILabelProvider provider = (ILabelProvider)
Platform.getAdapterManager().getAdapter(address, ILbelProvider.class);
System.out.println(provider.getText(address));
}

上記のAdapterManagerの仕組みが、型クラスに期待する振る舞いに概ね対応します。Eclipseアプリケーションを作っていて、祖結合のために型クラスの必要性を感じたら、その時はAdaptes Extension Pointを使用すると良いでしょう。
なお、Eclipse Adapterのランタイムの振る舞いはExtension Object Patternここに詳しいです。


Eclipse Adapterと型クラスとの違い
Eclipse AdapterとScalaやHaskellの型クラスとは決定的に異なる部分があります。
  • Eclipse Adapterは実行時の仕組みであり、静的な型保証はできない。
  • Adapterルックアップのために、AdapterManager APIに依存してしまう。
実装言語がJavaであり、Eclipseアーキテクチャがとても古いことを考えると、致し方ないところでしょうか。
それでも、モデルの型によるアダプタの選択により、アイデンティティ問題の回避に期待通りの効果を発揮してくれます。


ともあれ、Eclipseアプリケーションの開発でEclips Adapterの仕組みを理解していたおかげで、ScalaやHaskellの型クラスについても、割とスムーズにイメージできました。
それにしても、HaskellやScalaの表現力はすごいですね。

2011年8月30日火曜日

Asakusa DSLを知り、Xtext適用について夢想する

クラウド温泉2.0@小樽に参加して、Asakusa Frameworkについて、恥ずかしながら初めて知りました。そこで、Asakusaについてちょっと調べてみたことと併せて、Xtext応用の可能性について考えたことをメモします。
なお、Asakusaについての調査は、あくまで斜め読みですので、誤解してる箇所についてはご容赦頂き、指摘して頂けると有難く思います。


言語指向フレームワークとしてのAsakusa
Asakusaは、技術的な面だけでざっくり言うと、Hadoop用のMapReduceコードを生成するフレームワークで、開発者ロールによって異なる3種類のデータフローDSLと、データモデルDSLを持っている、本格的な言語指向フレームワークと理解しました。

  • 現時点で、データフローDSLはJavaをホスト言語とする言語内DSLで、データモデルDSLは専用の外部DSLのようです。このJava DSLは、実行時リフレクション方式ではなく、静的コード生成方式のようです。
  • Javaをホスト言語とする言語内DSLは一見珍しい気もしますが、アノテーションによるメタプログラム(DSL)からAPTでコード生成を行うのは、Javaによる静的メタプログラミング手法の一つであり、Slim3でもそうしたものを伺えます(だからといって真似が簡単だというわけでもないです...)。
  • 一方、データモデルDSLはDMDLスクリプトと呼ぶ外部DSLであり、DMDLコンパイラによってJavaモデルコードを生成するようです。
  • データフローDSLの場合も、データモデルDSLの場合も、結果として静的にJavaコードを生成する原材料で、Hadoop実行時には生成MapReduceコードが走る仕組みのようです。

Javaだけじゃない、ScalaによるDSL定義
ちょっと情報を探してみると、AsakusaデータフローDSLをJava以外の言語でも記述できるようにする計画があるらしく、最初の実装としてScalaが検討されているようです。

Javaの場合もScalaの場合も、汎用プログラミング言語内DSL(Scalaの場合はホスト言語内DSLとは言えない感じなので)ですが、いずれの場合も実行時方式ではなく静的コード生成方式なのは同じようです。
また、このような複数のDSL表現言語から、共通に利用できる内部表現形式を定義する予定もあるようです(もうあるのかもしれません)。
さらに、AsakusaのDSLコンパイラは、データフローを最適化圧縮する仕組みがあるらしく、内部表現形式がDSL定義言語から独立することで、このオプティマイザも再利用できるようになると思われます。
言語指向の王道設計パターンと言えばその通りですが、それがHadoopのような領域で実用となると、とてもすごい応用ですよね。


AsakusaにXtextを適用できるか
さて、このAsakusaのDSL定義として、Xtextを適用できるかどうかを、ぼんやり考えてみたので、ざっとメモとして挙げてみます。なお、設計や実装、ドキュメントに詳細に当たったわけではないので、かなり想像/妄想で考えていることについては、ご了承ください。誤りがある場合は、ご指摘頂けると有難いです。
  • AsakusaのメタプログラムDSLは、実行時リフレクションではなく静的コード生成方式なので、まずはXtextが行けそうな分野です。
  • Asakusaが、汎用言語内DSLを採用しているのは、エディタなどのDSL技術開発生産性や、IDEによる開発との親和性を考えてのことのようなので(出典)、この点はまさにXtextの得意分野と言えると思います。
    ただし、XtextのDSLエディタは、Eclipseプラグインとして生成されるので、DSLエディタ機能がEclipseにロックインされてしまうという、若干の懸念はあります(パーサなどはその限りではないです)。
  • AsakusaのDSL内部表現形式については、現時点では恐らくPOJOとして実装されているとは思いますが、それがEMFモデルとして定義されると完璧にXtextと調和します。
    AsakusaのDSL内部表現形式は、ランタイムでHadoop上で動作するものではなく、Javaコード生成に使われるだけのものだと思うので、EMFのランタイム特性については、気にする必要が無いと思います。
  • EMFで表される内部表現からのJavaコード生成も得意分野だと思います。
    自分はまだ不勉強ですが、XtendとMWEでJavaコードを生成するイメージになると思います。
  • Asakusaのワークフロー最適化については、DSL実装/内部表現形式/オプティマイザが全て独立していることにより、オプティマイザは再利用可能なので、Xtextを適用しても影響ないと思います。
  • ワークフローDSL以外に、DMDLからのJavaモデルコード生成にも、上記と全く同じ考え方で、Xtextを採用できると思います。
    ワークフローDSLは汎用言語内DSLとし、DMDLは外部DSLとしていることで、現状はそれぞれ別なDSL表現技術となっていると思いますが、Xtextを使って両方とも外部DSL方式とすれば、同じDSL表現技術で統一することができます(それが良いことなのかは自明ではありませんけれども...)。
  • DSLエディタやコードジェネレータの実装コストについては、Xtextでかなり削減できるのは間違いないですが、エディタにJDTほどの使い勝手を求めるとなると、(それが偉大であるが故に)結構な手間がかかることが想像できます。この辺については、まだ自分も突き詰めていないので、まだハッキリとはイメージできません。
  • Xtextを使って外部DSLとすることで、汎用言語内DSLでの実装に比べて、構文ノイズを確実に減らすことができます。これは、ユーザにとってメリットだと思いますし、コンサルティング上も有利に働くような気がします。
  • ホスト言語が持つ汎用機能は完全に使えなくなるため、DSLに対する制約の実装は確実に容易になります。この辺りでコストが下がれば、DSLエディタの使い勝手を向上させるコストに回せそうな気がします。

浅い理解
まだまだいろいろな検討事項があるとは思いますが、自分の中に沸き上がった興味については、一通り挙げられたように思います。
ただし上記の検討は、あくまでDSL一般論としてであって、自分はまだHadoop MRの仕組みや、Asakusaワークフローについて本質的に理解していないので、DSLの複雑性を過小に見積もっている可能性が高いです。その結果として、汎用言語内DSL方式からXtextによる外部DSL方式に移行するコストが、ごっそりと過小評価されてしまい、上記の検討が完全に外れた内容になっている可能性もあります。その点については、今後知識をアップデートしていきたいと考えています。


汎用言語内DSL方式に対して、外部DSLを作成するコストを、どれくらい抑えることができるのかは、Xtextにかけられる大きな期待の一つだと思いますし、自分がXtextを今後活用して行く上でもとても気になる部分です。
Asakusaをネタにしていろいろ検討することで、また少し検討が深まった気がします。

2011年8月24日水曜日

BIRTスタンドアロン利用とEMF

BIRTもEclipse RCP外のスタンドアロンで使用できます。
今回は、BIRTエンジンの初期化過程で、スタンドアロン時にどうするかを調べます。
なおこの例は、Eclipse.orgのサンプルから引用しています。

BIRT Report Engine APIの場合
Report Engineは、BIRTレポートデザイナでデザインされたレポートをレンダリングするエンジンで、典型的にはWebアプリとしてHTMLをレンダリングするのに使用できます。
Webアプリじゃなくても、スタンドアロンアプリケーションとして、HTMLやPDFなどをレンダリングすることができます。
EngineConfig config = new EngineConfig();
// WebAppの場合
IPlatformContext context = new PlatformServletContext(servletContext);
config.setPlatformContext(context);
// Eclipse RCP以外の場合(WebAppの場合を含む)
Platform.startup(config);

IReportEngineFactory factory =
(IReportEngineFactory) Platform.createFactoryObject(
IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
IReportEngine engine = factory.createReportEngine(config);
...

BIRT Chart Engine APIの場合
BIRTのReport Engineは、レンダリングの過程でチャートを描画する際、内部的にChart Engineを使用していますが、このChart Engineだけを個別に利用することができます。
Chart Engineは、Report Engineで使われる時のようにPNGなどの画像をレンダリングすることはもちろん、SwingやSWT上で直接チャートを描画することもできます。
PlatformConfig config = new PlatformConfig();
// Eclipse RCP以外の場合
config.setProperty("STANDALONE", true);

ChartEngine engine = ChartEngine.instance(config);
...

このように、BIRTもスタンドアロンで簡単に使用することができます。
今回の例では示しませんでしたが、BIRTレポートをデザインするDesign Engine APIも、スタンドアロンで使用できるようです。
Eclipse.orgでダウンロードできるサンプルには、これらのコードが含まれます。
http://eclipse.org/birt/phoenix/deploy/


EMF応用例としてのBIRT
BIRTを使ってみて面白いのは、BIRTのレポートデザインやチャートが、EMFモデルとして作られていることです。

BIRTのレポートデザイナは、レポートをグラフィカルにデザインし、それをレポートデザインファイルとして保存しますが、これはまさにEMFモデルをXMLリソースとしてシリアライズしたものです。
BIRTのレポートレンダリングエンジンは、このレポートデザインモデルをメディアにレンダリングします。
BIRTのチャートモデルも、レポートモデル同様にEMFモデルであり、BIRTのチャートレンダリングエンジンが、チャートモデルをメディアにレンダリングします。

このように見ると、BIRTのデザイナやレンダリングエンジンは、EMFの応用例としてとても参考になりますよね。

2011年8月22日月曜日

XtextやEMFをスタンドアロンで使用する

XtextやEMFで作成した成果物を、Eclipse外のスタンドアロン環境で使用する方法についてメモします。

XtextやEMFの成果物をEclipseアプリケーションとして実行する場合は、拡張ポイントメカニズムによってそれらは自動的に初期化されるので、その仕組みについて細かく意識する必要はありません。
しかし、Eclipse外のスタンドアロン環境で使用する場合には、そうした自動的な初期化は行われないため、自分で初期化呼び出しを実装する必要があります。

とは言え、複雑なことは何もありません。
以下のようにとても簡単に行うことができます。 

Xtex DSLパーサのスタンドアロン初期化
MyDslStandaloneSetup.doSetup();
このクラスは、Xtextアーティファクト生成時に作られているので、確認しましょう。
この呼び出しだけで以下のことが行われ、パーサやモデルを使用できるようになります。
  • DSL用EMFモデルの初期化
  • DSLパーサのためのGuiceインジェクタの構築
  • EMFリソースファクトリへのDSLパーサの登録

EMFモデルのスタンドアロン初期化
MymodelPackage.eINSTANCE.getMymodelFactory();
EMFリソースAPIにアクセスする前に、モデルパッケージがEMFパッケージレジストリに登録されている必要があります。
EMFのパッケージクラスやファクトリクラスにアクセスしていれば、そのインスタンス作成時の初期化処理で、その登録処理が行われるので、それらになんらかの方法でアクセスしていればOKです。

EMFジェネレータオプションによっては、上記パッケージインタフェースが生成されないことがあるので、その場合はなんらかの代替アクセス処理を行えば初期化されます。
例えば、以下のようにインプリクラスを使って直接的に初期化する方法もあります。
MymodelPackageImpl.init();

EMFモデルを参照するXtext DSLパーサの
スタンドアロン初期化
MymodelPackageImpl.init(); 
MyDslStandaloneSetup.doSetup();
DSL開発を、EMFモデル定義から始めて、後からそのEMFモデルに対してXtextグラマーを定義した場合の、スタンドアロン初期化方法です。
別定義されたEMFモデルを参照している場合は、Xtext DSLパーサの初期化処理では、EMFモデルは初期化されません。そのため、DSLパーサ初期化前に、EMFモデルの初期化を行わなければなりません。
ただこの場合も、それぞれの初期化を順に行うだけです。


このように、EMFやXtextは、Eclipse外のスタンドアロン環境でも簡単に使用できます。

Xtextグラマーのポイント(3)

もう少しだけ、Xtextグラマーの面白いポイントを見てみます。
The Grammar Language  から幾つか拾ってみます。

順不同グループ

Modifier:
    static?='static'? & final?='final'? & visibility=Visibility;
どの順で現れても良いが、それぞれの要素は一度の出現しか許容しないという制約は、DSLではよく使いますね。順不同グループは、まさにその時に使う構文で、「&」で候補を区切ります。
選択構文は「|」区切りだったのが、「&」になっただけなので、簡単ですね。
上記の例は、Javaの修飾子指定に似た例ですが、'static'と'final'と可視性(visibility)が、それぞれ順不同で一度だけ現れて良いことを示しています。
この例では、'static'と'final'は「?」がついているのでオプションですが、visibilityは必須修飾子となっていますね。また、staticとfinalは「?=」割当オペレータが使われているのでboolean属性、visibilityは通常の「=」割当オペレータが使われているので、必須属性(または参照)としてモデル化されます。

列挙
enum Visibility:
    PUBLIC='public' | PRIVATE='private' | PROTECTED='protected';
一つの属性の値として現れて良い終端文字列を、いくつかの候補に制約したいことも、DSLでは頻繁にありますよね。その場合に使うのが列挙です。
Xtextでの列挙は、上記のようにまさに「enum」終端ルールとして宣言します。
左辺(PUBLICなど)はモデル上のenum定数名になり、右辺('public'など)はDSL上の終端文字列になります。右辺は省略可能で、省略時は左辺と同じ文字列が終端文字列になります。
列挙は、Javaのenum型としてモデル化されるので、とても扱いやすいですし、DSLエディタ上でもコンテンツアシストされて、とても便利です。


データタイプ(一般的な終端ルール)

QualifiedName returns ecore::EString :
  ID ('.' ID)*
;
構文ルールが展開されると、最終的には終端ルールが明確になる必要があります。
これまで見てきた、列挙やキーワードオプション、クロスリファレンスのような特別な終端ルールもありますが、通常は、識別子、文字列リテラル、数値リテラルなどの、一般的な終端ルールが必要になります。

Xtextでの終端ルールは、テキストパターンとモデル型を定義します。
上記の例では、1行目の「QualifiedName」は終端ルール名で、「returns」句の右の「ecore::EString」は、この終端ルール適用時に返されるモデル型を示します。「returns」句を省略すると、モデル型は文字列型になります。
2行目の「ID('.' ID)*」は、この終端のテキストパターンを示しています。
ID終端ルールは、グラマー宣言で引き込んでいる既定のCommonTerminalsから再利用していますが、内容は一般的な識別子の正規表現パターン(先頭英字で残りは英数字)になっています。
この他にも、CommonTerminalsには、INTやStringなどのよく利用する終端ルールが定義されているので、簡単なDSLを作る際にはそのまま利用することができます。

Xtextの終端ルールが、任意のモデル型を指定できるのは、とても大きな特徴です。
これは、自由なモデル型のリテラル(相当)を作れることを意味しています。
例えば、「2011-01-23」などを日付リテラルとして扱ったり、特別な制約がある数値的リテラルを作ったりもできます。
独自のモデル型の終端や、独自の制約を持つ終端を定義するには、文字パターンの定義だけではなく、モデル上で使用する実際のJava型の存在と、文字パターンからそのJava型に変換するValueConverterの実装が必要です。
これについては、また別途まとめようと思います。


2011年8月20日土曜日

Xtextグラマーのポイント(2)

さらに 15 Minutes Tutorial のサンプルから、Xtextグラマーのポイントを追ってみます。
今回は、クロスリファレンスなどのXtextの面白い機能も見てみましょう。

grammar org.example.domainmodel.DomainModel with
                                      org.eclipse.xtext.common.Terminals

generate domainmodel "http://www.example.org/domainmodel/Domainmodel"

Domainmodel :
  elements += Type*
;

Type:
  DataType | Entity
;

DataType:
  'datatype' name = ID
;

Entity:
  'entity' name = ID ('extends' superType = [Entity])? '{'
     features += Feature*
  '}'
;

Feature:
  many?='many'? name = ID ':' type = [Type]
;

選択は継承としてモデル化される
Type:  DataType | Entity;
この場合、Typeルールが適用される場所では、DataTypeまたはEntityのいずれかのルールが適用されるというのがグラマー的な理解ですが、モデル的にはTypeはDataTypeとEntityの汎化として扱われます。つまり、基底型Typeを継承したDataType型とEntity型が生成されます。
DataTypeとEntityのいずれが選択されるかに曖昧さが生じないよう注意が必要です。DataTypeとEntityルールには、どちらも先頭にキーワード('datatype'や'entity')が定義されており、出現するキーワードによってルール選択の曖昧さが生じないようになっています。
もし構文に曖昧さがある場合、グラマーコンパイル時のエラーでそれが分かります。

オプション構文とキーワード
Entity:
  'entity' name = ID ('extends' superType = [Entity])? '{'
     features += Feature*
  '}'
;
この場合、'extends'キーワードとそれに続くsuperType=[Entity]の項はオプションです。
「?」は正規表現同様、0〜1回の出現、すなわちオプションであることを意味しています。キーワードを含む割当構文全体がオプションとなります。
この場合はオプション構文が1つしか無いので、'extends'キーワードが無くても曖昧さは生じませんが、複数のオプション構文がある場合は、何が省略されているのかが曖昧になるので、多くの場合オプション構文にキーワードが含まれる必要があります。

キーワードの有無自体のオプション構文

Feature:
  many?='many'? name = ID ':' type = [Type]
;
 'many'キーワードの有無自体がモデル上の情報になるオプション構文です。
「?=」オペレータは、右辺の構文が現れた場合のみ左辺が真になる割当構文です。
この時、Featureモデルのmany属性はboolean型(isMany())としてモデル化されます。

クロスリファレンス
クロスリファレンスの仕組みは、Xtextの面白い強力な機能の一つです。DSL内でユーザが定義した識別子を、DSL内の別な箇所で参照する機能です。

例えば、プログラミング言語の型名や変数名のように、ユーザがDSL内のどこかで定義/宣言した識別子を、他所で指定することが多くありますが、このような場合に使用できるのが、このクロスリファレンスの機能です。
生成されるDSLエディタ上でも、他所で定義した識別子が、参照時にコンテンツアシストされるようになり、とても強力です。
'extends' superType = [Entity] 
type = [Type]
このように右辺が [ルール] となっている割当構文は、クロスリファレンスになります。
このサンプルの例では、ユーザがDSL内で「型」を定義したあと、別な場所でその定義済みの「型名」を指定できるようにしています。

通常の割当構文では右辺でルール呼び出しした結果がフィールドに格納されるため包含参照になりますが、クロスリファレンスの場合のフィールドは非包含参照になります(他所で定義された値を参照するのですから当然ですが)。
(ここで言う包含参照とは、EMFにおけるcontainment=trueな参照モデルです)

クロスリファレンスの参照先の指定には、若干の注意が必要です。
  • DSL上の参照は、参照先モデルの「name」属性と同じ値を、「ID」終端ルールにより記述することで行われます。
    このルールに従わないと、クロスリファレンスは機能しません。
    (変更することは可能です)
  • 参照先名称は、厳密にはルール名ではなくて、モデルのEClass名です。
    Xtextではルール名とモデル名を別定義できるので、その場合は要注意です。


これらのサンプルテクニックを覚えると、ちょっとしたDSLをすぐに作れそうですよね。
どの構文も簡単ですし、どのようにモデル化されるかも分かりやすいと思います。

2011年8月19日金曜日

Xtextグラマーのポイント(1)

Xtext Documentationにある 15 Minutes Tutorial の最小のサンプルから、Xtextグラマーのポイントをおさえてみます。

grammar org.example.domainmodel.Domainmodel with
                                      org.eclipse.xtext.common.Terminals
generate domainmodel "http://www.example.org/domainmodel/Domainmodel"

Model:
    greetings+=Greeting*;

Greeting:
    'Hello' name=ID '!';

grammer宣言

grammar org.example.domainmodel.Domainmodel with                                      org.eclipse.xtext.common.Terminals
このDSLグラマーの名前を宣言しています。
グラマー名は、モデルのJavaパッケージ名と、関連クラス名に反映されます。
with句でmix-inする他のグラマー名を指定すると、他の構文を取り込めます。
この例では、標準の終端定義を取り込んでいます。


generate宣言

generate domainmodel "http://www.example.org/domainmodel/Domainmodel"
EMFのEcoreモデルとJavaモデルのクラスの生成を指示します。
名前部分は、EPackage名とDSLファイル拡張子に反映されます。
URI部分は、EcoreモデルのnsURIに反映されます。

ルール定義
Model:Greeting:
の部分はルールを新たに定義しています。
ルール名は、グラマーの中で子要素のルールとして参照されます。
また、ルール名はそのままモデルクラスとして生成されます。

子要素と割当
greetings+=Greeting*;
 'Hello' name=ID '!';
の部分は、子要素への適用ルール宣言と、フィールドへの割当を定義しています。
左辺は、ルールで表されるモデルのフィールド名になります。

"greetings"の"+="オペレータは、複数要素を許容するためリストになります。
右辺は、適用されるルールです。
"Greeting*" は正規表現同様、0回以上の複数回の出現を許容しています。
右辺が終端(ID)の場合は、属性メンバーとしてモデル化されます。
右辺が非終端ルール(Greeting)の場合は、参照メンバーとしてモデル化されます。
なお、クロスリファレンスじゃない子要素は、包含参照としてモデル化されます。

キーワード
'Hello' name=ID '!';
の、クォートで囲まれた箇所は、キーワードになります。
キーワードはDSL中にそのままの形、順序で出現すること求められます。
複数ルールが許容される局面で、選択ルールを判別するためにも使用されます。
キーワードはエディタでコンテンツアシストもされます。


どうでしょうか。
グラマーと併せてモデルも簡単に定義できることがお分かり頂けたでしょうか。

Xtextグラマーを学ぶ

XtextによるDSL開発はグラマー定義から始めます。
グラマー定義の基礎を一通り試すには、以下のチュートリアルがお勧めです。


Xtext 15 Minutes Tutorial

DSL例

datatype String

entity Blog {
    title: String
    many posts: Post
}

entity HasAuthor {
    author: String
}

entity Post extends HasAuthor {
    title: String
    content: String
    many comments: Comment
}

entity Comment extends HasAuthor {
    content: String
}
グラマー例

grammar org.example.domainmodel.DomainModel with
                                      org.eclipse.xtext.common.Terminals

generate domainmodel "http://www.example.org/domainmodel/Domainmodel"

Domainmodel :
  elements += Type*
;

Type:
  DataType | Entity
;

DataType:
  'datatype' name = ID
;

Entity:
  'entity' name = ID ('extends' superType = [Entity])? '{'
     features += Feature*
  '}'
;

Feature:
  many?='many'? name = ID ':' type = [Type]
;

このチュートリアルでは、Eclipse上でのXtextプロジェクトの作り方を含めて、サンプルをもとにしたグラマー定義を体験できます。実際にDSLでよく現れる問題に、Xtextの構文定義をどのように行うべきかを学ぶことができます。
  • 構文ルールの開始や有無を識別するキーワードの定義
  • 構文ルール内の要素をモデルのフィールドへ割当
  • 構文ルールが複数の子要素を持つ場合の定義と値の獲得
  • 構文ルールが複数の構文ルールの選択である場合の定義
  • ユーザ定義識別子へのクロスリファレンスの定義方法
このチュートリアルでは、グラマー定義の本質的な部分を学ぶことが出来ますが、例えば以下のようなことは含まれていません。
  • 終端(Terminal)と値型の定義
  • 列挙による終端定義
  • ルールとEMFモデルとの対応のカスタマイズ
こうした、より深いテーマについては、以下のリファレンスで学ぶことが出来ます。リファレンスとは言っても、こちらもまた分かりやすいサンプルをもとにしているので、とても分かりやすいです。

The Xtext Grammar Language

このリファレンスでは、グラマーの細かい部分まで学ぶことができるので、15分チュートリアルが終わったら、さっと目を通してみると良いでしょう。

Xtextで最小限覚える3つの要素

XtextでDSL開発を行うときに、最小限覚える必要があるのは、以下の3つの要素かと思います。

構文定義(Grammar)
Xtextはパーサ生成式の外部DSL技術なので、何よりも構文定義が必要です。
構文定義は、シンプルなXtext文法に従って行います。
文法は、構文定義、終端定義、モデル定義を兼ねていて、扱いやすいです。

終端、値型、ValueConverter、RuntimeModule
終端は字句パターンとしてだけでなく、何らかの値型として定義します。
標準の終端と値型だけ利用する場合は、これらの特別な定義は不要です。
本格的なDSLを作る場合、制約を設けるために独自の定義が必要になります。
その場合、終端の字句を値型に変換するValueConverterを実装します。
ValueConverterは、RuntimeModuleを実装してパーサに組み込みます。

モデルクラス/リソースAPI
構文からコード生成するとモデルクラスやパーサ/エディタが生成されます。
モデルクラスはEMFモデルであり、Ecoreファイルも一緒に生成されます。
DSLを読み込んでモデルを得るには、EMFリソースAPIを使用します。
パーサはリソースAPIの背後で自動的に使用されます。
エディタはEclipseアプリケーションとして実行できます。
なお既存EMFモデルからDSL開発を始めた場合、モデルは生成されません。
パーサやエディタは、既存EMFモデルを使用するように生成されます。

2011年8月18日木曜日

DSL開発を構文から始めるかモデルから始めるか

Xtextを使ったDSL開発は、XtextでDSL構文定義から始める方法と、EMFでモデル定義から始める方法の2種類があります。

DSL開発ということで、何よりもまずシンタックスを設計したい人にとっては、まず構文定義から始めて、徐々にモデル=内部表現形式について考えたいかもしれません。Xtextでは、構文定義と同時にモデルの定義/マッピングも行うことができまるので、DSL構文から自然にモデルを導出できる利点があります。

一方で、シンタックスよりもモデル=内部表現形式の設計を重んじる人にとっては、まずモデル設計から始めて、後で構文定義を作る方がしっくり来るかもしれません。Xtextでは、既存EMFモデルからそれに対応する構文定義を仮生成してくれるので、モデルに対応するDSL構文を簡単にできる利点があります。

Xtextはこのどちらのアプローチをとることもできます。

私の今のプロジェクトでは、EMFモデルを使用するサービスのテストデータ作成用にXtextを使ったので、後者のスタイルを採用しましたが、さしあたってXtextの使い方を覚えるには、前者の構文定義から始めるのが手軽でしょう。

EMFやXtextはEclipse外でも使えます

EMFやXtextに興味を持った人がこれを採用しない理由に、「Eclipseの外では使えない技術」という錯覚があるようです。
実際には、EMFとXtextのどちらも、Eclipse外のランタイム環境で、普通のJavaライブラリとして使えます。

Eclipse外のランタイム環境で使えるもの
  • EMFモデル(生成コード)
  • EMFリソースAPI(シリアライザ)
  • EMFメタモデルAPI(リフレクション)
  • Xtext DSLパーサ/シリアライザ(生成コード)
Eclipseプラグインとしてのみ使えるもの
  • EMFモデルXMIエディタ
  • DSLエディタ
DSLを読み込んで動作するアプリケーションは、普通のJavaアプリケーションとして作ることができます。例えば、Eclipseアプリケーション上で編集したDSLを、サーバサイドアプリや組み込みアプリに取り込んで動作させることができるんです。

Xtextで再注目したいEMF

いまの仕事では、EMF (Eclipse Modeling Framework)を使ってデータモデルを扱っています。
また、やや複雑なモデルを扱うテストは少々やっかいなので、テストでのモデルデータ記述を簡単にするために、DSL開発ツールのXtextを使っています。

この例のプロダクションコードにおけるEMFの効果は、デザインした静的メタモデルから単純なJavaコードを生成して使っているだけなので、さほどのありがたみも無いですし、「クラス図からコード生成して喜ぶなんていつの流行だよ」と言いたくもなりますね。

だけど、テストデータの作成/使用時などの局面で、XtextというパワフルなDSL環境を活かせることを考えると、EMFを単なるJavaコード生成ツールという理解で済ませるのは、ちょっと勿体ないです。EMFのコアは、静的モデリングとコード生成というシンプルなものですが、周辺フレームワークと連動させることで、とても強力なツールに化けるんです。
  • EMFデフォルトのXMLシリアライザでも良いのですが、人手でテストデータを書くことを考えると、Xtextの方がありがたいです。
  • メタモデルとXMIだけで元々十分強力とも言えますけれども。
ところで、EMFはとてもシンプルなのですが、ちょっと勉強して挫折したという人が、意外と多いように思います。ネット上での良い日本語情報は少ないですし、実際どんなありがたみがあるのかイメージしづらいことや、多くの人がそれを使いたい(使わないと困る)と思うほどのキラーアプリケーションの登場が遅かったせいもあるかもしれません。

でもここ数年、DSLが注目を浴び、Xtextが現れてから、再び状況は変わって来ました。Xtextの登場によって、EMFはより親しみやすいテクノロジになったと思います。
そこで、私もまだ勉強中ですが、EMFとXtextに関するメモを、幾つかしたためていきたいと思っています。