動作する言語ワークベンチ - MPS
言語ワークベンチを使用することは、従来のドメイン特化言語(DSL)での作業とは大きく異なります。これは、JetBrains Meta Programming System(MPS)を使用して、小規模ながらも興味深いDSLを構築する例です。これを使用して、言語ワークベンチでの作業がどのようなものかを感じることができます。
2005年6月12日
Contents(目次)
(言語指向プログラミングと言語ワークベンチ(少なくとも私の用語の使い方)に慣れていない場合は、言語ワークベンチに関する概要記事を読んでください。この記事では、言語ワークベンチを使用する例について説明し、その記事で説明した概念に精通していることを前提としています。)
これらの言語ワークベンチの1つは、JetBrainsのMeta-Programming System(MPS)です。言語ワークベンチの記事を書いていたとき、実際の言語ワークベンチでのより具体的な例を含めて、そのようなツールで作業するのがどのようなものかをよりよく理解してもらいたいと思いました。この例は説明が長くなったため、独自の記事として分割することにしました。
MPSを使用することにしたのは、どの言語ワークベンチが最適かについての意見があるからではなく(結局のところ、それらはまだ開発の非常に初期段階にあるため)、単にJetBrainsのオフィスが私の住んでいる場所からすぐ近くにあるからです。運転距離が短いほど、コラボレーションがはるかに容易になります。したがって、これを読むときは、MPSを見ることだけがポイントの一部であることを忘れないでください。この記事の本当のポイントは、このクラスのツールがどのようなものかを感じてもらうことです。表面上は各ツールは非常に異なりますが、多くの基本的な概念を共有しています。
JetBrainsは、Early Access ProgramでMPSを公開しました。これにより、開発バージョンのMPSをダウンロードして試すことができます。この記事の例はそこにあります。ただし、このツールはまだ開発中であるため、現在表示される内容と、これを書いているときに表示される内容は大きく異なる場合があることに注意してください。特に、特定の画面が変更されている可能性があり、変更ごとにここのスクリーンショットを最新の状態に保つことは想定していません。また、まだ開発中の新しい種類のツールに典型的な、多数の未完成な部分もあります。ただし、重要なのは原則なので、見てみる価値はあると思います。
Agreement DSL(契約DSL)
この例では、何度か遭遇したパターンを使用しています。これは現在、契約ディスパッチャーと呼んでいます。契約ディスパッチャーの背後にある考え方は、システムが外部の世界からイベントを受信し、さまざまな要因によりイベントに異なる反応をするということです。その主な要因は、ホスト企業とイベントに関する当事者との間の契約です。
おそらく、これについてさらに説明する最も簡単な方法は、例として使用するDSLの例を示すことです。

図1:通常プランの契約DSL。
このDSLの部分は、架空の公益事業会社が、通常のプランの顧客のイベントにどのように反応するかを示しています。契約定義は、値とイベントハンドラーで構成され、どちらも時間とともに変化します。
この契約には、顧客に請求される電気の基本料金という1つの値があります。1999年10月1日から1キロワット時あたり10ドルに設定され、12月1日には12ドル/キロワット時に急激に引き上げられました。
契約は、3種類のイベントへの反応を示しています。使用量(電気の)、サービスコール(メーターを修理するための人が出入りするなど)、および税金です。ハンドラーは基本料金と同じように時間的です。サービスコールのハンドラーも12月1日に変更されたことがわかります。
ハンドラーは、アカウントへの金銭的価値の転記という単純な反応を示します。アカウントはDSLで直接記述され、金額は式を使用して計算されます。式には、契約で定義された値とイベントのプロパティを含めることができます。使用量イベントには、この請求期間に使用された電力量を示す使用量プロパティが含まれています。USAGEイベントの転記ルールは、使用量イベントが発生したときに、この使用量と基本料金の積を顧客の基本使用量アカウントに転記することを示しています。

図2:低所得者向けの別のDSLフラグメント。
図2は、特別なプランの低所得者向けの2番目の契約を示しています。これに追加された唯一の興味深い点は、ここでの使用量計算式に、Excel構文を使用して表される条件が含まれていることです。
これらのフラグメントについて最初に注意すべき点は、それらが非常にドメイン指向であり、ドメインの観点から読みやすいということです。COBOLの推論が私を悩ませますが、プログラマーではないドメインエキスパートにも読みやすいと言えるでしょう。
これらのDSLフラグメントは、Javaで記述されたフレームワークに適合するコードを生成します。実際、これらのDSLは、契約ディスパッチャーの説明で使用したものと同じシナリオを記述しています。
比較のために、Javaで記述された同じ構成コードを次に示します。
public class AgreementRegistryBuilder { public void setUp(AgreementRegistry registry) { registry.register("lowPay", setUpLowPay()); registry.register("regular", setUpRegular()); } public ServiceAgreement setUpLowPay() { ServiceAgreement result = new ServiceAgreement(); result.registerValue("BASE_RATE"); result.setValue("BASE_RATE", 10.0, MfDate.PAST); result.registerValue("CAP"); result.setValue("CAP", new Quantity(50, Unit.KWH), MfDate.PAST); result.setValue("CAP", new Quantity(60, Unit.KWH), new MfDate(1999, 12, 1)); result.registerValue("REDUCED_RATE"); result.setValue("REDUCED_RATE", 5.0, MfDate.PAST); result.addPostingRule(EventType.USAGE, new PoorCapPR(AccountType.BASE_USAGE, true), new MfDate(1999, 10, 1)); result.addPostingRule(EventType.SERVICE_CALL, new AmountFormulaPR(0, Money.dollars(10), AccountType.SERVICE, true), new MfDate(1999, 10, 1)); result.addPostingRule(EventType.TAX, new AmountFormulaPR(0.055, Money.dollars(0), AccountType.TAX, false), new MfDate(1999, 10, 1)); return result; } public ServiceAgreement setUpRegular() { ServiceAgreement result = new ServiceAgreement(); result.registerValue("BASE_RATE"); result.setValue("BASE_RATE", 10.0, MfDate.PAST); result.setValue("BASE_RATE", 12.0, new MfDate(1999, 12, 1)); result.addPostingRule(EventType.USAGE, new MultiplyByRatePR(AccountType.BASE_USAGE, true), new MfDate(1999, 10, 1)); result.addPostingRule(EventType.SERVICE_CALL, new AmountFormulaPR(0.5, Money.dollars(10), AccountType.SERVICE, true), new MfDate(1999, 10, 1)); result.addPostingRule(EventType.SERVICE_CALL, new AmountFormulaPR(0.5, Money.dollars(15), AccountType.SERVICE, true), new MfDate(1999, 12, 1)); result.addPostingRule(EventType.TAX, new AmountFormulaPR(0.055, Money.dollars(0), AccountType.TAX, false), new MfDate(1999, 10, 1)); return result; } }
構成コードはまったく同じではありません。転記ルールには、まだDSLに追加していない課税対象のブールマーカーが付いています。さらに、式は、最も一般的なケースでパラメーター化できるさまざまなJavaクラスに置き換えられます。これは、Javaソリューションで式を動的に作成しようとするよりも優れていることがよくあります。しかし、基本的なメッセージは伝わっていると思います。Javaの文法が邪魔になるため、Javaでドメインロジックを見るのははるかに困難です。これは、プログラマー以外の人にとっては特にそうです。
(結果のフレームワークが実際にどのように機能するかに興味がある場合は、契約ディスパッチャパターンをご覧ください。ここでは説明しません。そのパターンの例は似ていますが、まったく同じではありません。)
DSLの例では、テキストではなくスクリーンショットが使用されていることに気付いたかもしれません。それは、DSLはテキストのように見えますが、実際にはテキストではないためです。代わりに、それらは基礎となる抽象表現の投影であり、エディターで操作する投影です。

図3:新しいレートの追加
図3はこれを示しています。ここでは、新しい基本料金を追加しています。エディターは、入力する必要があるフィールドを示し、必要に応じて適切な値を入力します。実際にはあまりテキストを入力しません。多くの場合、主なタスクはピックリストから選択することです。現時点では、日付は構造化された数値として入力されますが、完全に開発されたシステムでは、カレンダーウィジェットを使用して日付を入力できます。
これの最も興味深い要素の1つは、プランでExcelスタイルの式を使用することです。式に項を追加するときのエディターを次に示します。

図4:式の編集。
ポップアップには、式で必要なさまざまな式、プランで定義された値、およびこのコンテキストで処理されているイベントのプロパティが含まれていることに注意してください。エディターは、プログラマーがコードを正しく入力するのを支援するために、コンテキストに関する多くの知識を使用しています。これは、IntelliJ以降のIDEが行っているのと同じです。
式に関するもう1つのポイントは、契約を定義するために使用される言語とは別の言語から来ていることです。したがって、Excelのような式を使用する必要があるDSLは、すべての定義を自分で作成することなく、式を言語にインポートできます。さらに、これらの式は、式言語を使用している言語の記号を組み込むことができます。これは、言語ワークベンチが目指す象徴的な統合の良い例です。他の人が定義した言語を使用できる必要がありますが、同時に、独自の言語に可能な限りシームレスにそれらを織り込む必要があります。
(完全な開示のポイントとして、この式言語は実際にはこの例を開発する際に応答して記述されましたが、他の言語で使用できるように分離されています。これは、開発中のツールを見ているという事実の偶然です。MPSの開発哲学とともに:MPSの興味深いアプリケーションを見つけ、これらのアプリケーションのニーズを使用してMPSの機能と設計を推進します。これは私が好む開発哲学です。)
最後のスクリーンショットは、別の重要なポイントを示しています。式に切り替えたときに、新しいレートの作業を完了しなかったことに気付くでしょう。この種のインテリジェントな、つまり構造化されたエディターの過去の問題の1つは、正しくない入力を処理できなかったことです。先に進む前に、各入力ビットが正しくなる必要があります。このような要件は、使いやすさの問題です。プログラミングを行うときは、無効な情報をそのままにしておく必要がある場合でも、簡単に切り替えることができる必要があります。その結果、投影エディターの場合、抽象表現で無効な情報を処理できる必要があります。実際、これを行うことができ、それでもできるだけ機能できる必要があります。この場合の1つのオプションは、エラーのある時間要素を無視して、プランからコードを生成することです。このような、意図的な無効性に対する堅牢な動作は、言語ワークベンチの重要な機能です。
ここでのMPSの例では、テキストのような投影を使用しています。MPSはこれまで、この種の投影に焦点を当ててきました。対照的に、MicrosoftのDSLツールはグラフィカルな投影に焦点を当てています。ツールの開発が進むにつれて、テキストとグラフィカルの両方の投影が提供されるようになると予想しています。モデリング関係者が「百聞は一見に如かず」と言うことに執着しているにもかかわらず、テキスト表現は依然として非常に役立ちます。成熟した言語ワークベンチは、テキストとグラフィカルの両方の投影をサポートすると期待しています。多くの人がプログラミング環境と考えていない投影とともに。
スキーマの定義
言語がどのように見えるかを確認できたので、どのように定義するかを見てみましょう。ここでは言語定義全体を説明するのではなく、どのように機能するかを感じてもらうためにいくつかのハイライトを取り上げます。

図5:プランのスキーマ
図5は、plan構造体のスキーマを示しています。(左側に、この契約言語の他の概念のリストも示しています。)データモデリング、特にメタモデリングを行ったことがある場合、これは驚くべきことではありません。ここでは、定義のすべての要素ではなく、ハイライトのみを説明します。いつものように、これは現在流動的であるため、おそらくこれとは少し異なって見えることを忘れないでください。
概念を定義し、他の概念を拡張(継承)できるようにします。インスタンスレベルと概念(クラスレベル)の両方で、概念にプロパティとリンク(属性と関係に似ています)を与えることができます。リンクでは、多重度(双方向)とターゲットの概念を示します。
この場合、プランは複数の値とイベントで構成されており、それぞれに独自の定義があります。図6は、非常に単純なイベントの定義を示しています。

図6:イベントのスキーマ
posting rule temporalプロパティに新しいものがあります。値とposting ruleはどちらもこの種の時間的ルールに支配されるため、日付キー付きロジックを持つ共通の機能を factoring out することは理にかなっています。そのため、時間的プロパティ定義(図7)と、posting ruleの時間的プロパティによる拡張(図8)の両方があります。

図7:時間的プロパティのスキーマ

図8:posting ruleへの時間的リンクのスキーマ。
この場合、時間的プロパティは、有効日付と値の概念を定義します。posting ruleの時間的プロパティはこれを拡張しますが、オブジェクト指向言語の継承とは少し異なる方法で行います。新しいリンクを追加するのではなく、この値リンクを特殊化して、posting ruleにのみリンクできるようにします。これは、プログラミング言語でジェネリクスを使用して実現することと似ています。関係を特殊化するこのアイデアは、UMLを含むいくつかのモデリング言語に存在します。ほとんどのモデリングではそれほど役に立ちませんでしたが、メタモデリングには非常に便利です。これは、特定の形式の制約と考えることができます。
最後に、posting rule自体がどのように定義されているかを示します。

図9:posting ruleのスキーマ。
これは、実際には別の数式言語の一部である数式と呼ばれる概念を拡張します。

図10:別の言語からの数式のスキーマ。
これにより、MPSでスキーマを設定するために必要なものがわかります。概念ごとに、要素間のさまざまなリンクを作成して定義を編集します。データモデルまたはUMLのようなクラス図の方がここではうまくいくのではないかと思います。これは、図式形式でうまくいく種類のことです。ただし、このスタイルのエディターも非常にうまく機能し、新しい言語スキーマをかなり迅速に入力できます。
あなたが私が望むことを考えているなら、あなたは何か他のことに気づいたでしょう。スキーマを編集するための画面は、DSLを編集するための画面と非常によく似ています。ご想像のとおり、スキーマを編集するためのDSL(MPSの構造言語と呼ばれます)があります。そのDSLの一部であるエディターを使用してスキーマを編集します。この種のメタサーキュラーブートストラップは、言語ワークベンチでは一般的です。
エディタの構築
次に、MPSでエディターを定義する方法を見てみましょう。

図11:プランのエディター定義。
図11は、プランのエディターを示しています。一般に、モデル内の概念ごとにエディターを構築します。(厳密に1対1ではありませんが、考え始めるには良い場所です。)プランのエディターを定義するには、エディターのエディターを使用します(ここではメタサーキュラリティを回避するのが難しくなっています)。エディターはセルの階層として定義されます。階層のリーフは、定数、またはスキーマ内の要素への参照にすることができます。エディターエディター(現在見ているもの)は、エディターの一部を区切るためにいくつかの記号を使用します。これらの記号は少し不可解ですが、表記法は非常に簡単に変更できるため、言語ワークベンチの表記法について心配する必要はありません。
このセル階層の最上位は、エディター全体のセルコレクションです。「[/」セルを選択することで、これを選択します。

図12:セル階層の最上位の選択。
エディターエディターを使用している場合、以前に使用しなかったインスペクターフレーム(左下)が重要になります。インスペクターは、GUIビルダーでプロパティエディターが使用されるのと同じ方法で使用されます。ここでは、インスペクターは垂直セルコレクションがあることを示しています。サブセルは次のとおりです。
- `[> plan` で始まる行
- 空白行
- `% value %`とその次の行を含む行。
- 別の空白行
- `% event %`とその次の行を含む行。
ご覧のとおり、この投影の問題の1つは、実際のセル階層を把握するのが難しいことです。インデントと空白を表示するために空白セルを使用することも疑問です。将来的には、エディターエディターの使い勝手を向上させる方法について、さらに多くの作業が行われると予想しています。
空白以外の3行は、プランに名前を付ける行、値の行、およびプランエディターのイベントの行に対応しています。

図1:通常のプランの例をもう一度示します。プランエディターの空白以外の3行が、プランの3つのコンテンツ領域(名前、値、イベント)にどのように対応しているかを確認してください。
次に、これらのコンテンツ領域の最初の領域であるプランの名前について詳しく説明します。これが最も簡単な領域であるということが役に立ちますが、それでも、エディターエディターはインスペクターを使用して多くの情報を提供するため、このような記事で説明するのは困難です。結果として、多くのスクリーンショットを使用する必要があります。

図14:プランラインのセルコレクション。
プランの名前は、プランエディターのセルコレクション全体内の単一のセルに表示されます。このセルはセルコレクションであり、今回は2つのサブセル(定数とプロパティ)の水平コレクションです。(エディターエディターは、垂直セルコレクションを[/で、水平コレクションを[>で示します。)

図15:プランラインの単語「plan」の定数。
定数は、単なる作業計画です。定数セルを使用して、マーカーやヒントをエディターに配置できます。また、空白の定数セルを使用して、空白行やインデントなどのレイアウトを行うこともできます。プランエディターの区切り文字([/や[>など)も、エディターのエディターで定義された定数です。

図16:プランラインの名前のプロパティ。
プロパティセルでプランの名前を表示します。プロパティは、エディターが定義している概念の任意のプロパティにすることができます。ここでは、インスペクターのプロパティフィールドを編集していることを示しており、プランの概念に関するすべてのプロパティを示すポップアップがあります。この場合は1つだけです。
エディターの空白行は、単純な定数セルです。値とイベントの行には、サブエディターが含まれます。値の行をスキップして、イベントを詳しく調べます。
イベント行は、垂直セルコレクション内のセルであり、それ自体が2つのサブセルを持つ水平セルコレクションです。空白の定数セルと、(>でマークされたrefノードリストセルです。

図17:イベントのリンクセル。
リンクノードには、これまでに見てきた他のノードよりもかなり複雑なインスペクターがありますが、ここで興味深いのは2つの情報です。名前から推測できるように、refノードリストセルは、スキーマ内のリンクに従って要素をリストします。エディターは、どのリンクをたどるか、リストを垂直に作成する必要があることを指示します。図17では、エディターペイン自体でリンクを選択するためのポップアップ(今回は実際に選択肢があります)を示しています。インスペクターで行うこともできます。エディターペインには、`%`区切り文字で囲まれたリンク名が表示されます。
この小さな例は、エディターを定義するときに興味深い疑問を提起します。別のインスペクターを使用して編集するか、エディターペイン自体で直接編集する必要がありますか?エディターペインから物を遠ざけると、エディターペインに全体的な構造を取得し、エディター定義とエディターの使用結果の関係をよりよく理解できます。ただし、すべてをインスペクターに配置すると、何がセルにあるかを確認するために常に掘り下げることになります。これは、簡潔なマーカー([/や[>など)の正当化の一部です。マーカーをクリックしてインスペクターで何であるかを確認できますが、その特定のエディターに慣れると、エディターペインを直接読むことに慣れます。慣れると、簡潔さは、より少ないスペースでより多くのものを見ることができるため、役に立ちます。
また、さまざまな目的のための複数のエディター、人々の経験に合うもの、人々の好みに合うだけのものを想像することもできます。たとえば、意図的なエディターでは、多くの場合、好みに応じて異なる投影間をすばやく切り替えることができます。このようなネストされたテーブルを編集する場合、ネストされたテーブル(boxyと呼ばれる)、lispのような表現(lispy)、またはプロパティを持つツリービュー(かわいい名前なし)を切り替えることができます。条件付きロジックを編集するには、Cのようなプログラミング言語ビュー、または表形式の表現があります。単純な投影間の簡単な変更からより多くのことを理解できることが多いため、投影間のこの迅速な切り替えは役立ちます。
しかし、例に戻りましょう。プランエディターには、イベントを垂直にリストするセルがあることがわかりました。これらのイベントをどのように編集しますか?この時点で、イベントエディターに切り替えます。最終的なツールは、これらのイベントエディターをプランエディターに埋め込みます。
エディターを見る前に、イベントのスキーマを更新しましょう。

図6:イベントのスキーマ
編集プレーンにあるものだけを使用して、イベントエディターの定義を次に示します。

図19:イベントエディターのエディターペイン定義。
簡潔な記号に目が慣れてきたら、インスペクターを使用せずにこのほとんどを取得できるはずです。基本的に、2つの要素を持つ垂直セルコレクションがあります。2つの下部は、その概念のために定義されたエディターを使用するposting ruleの時間的プロパティをリストするためのrefノードリストセルです。ただし、一番上のセルには、まだ見ていないものがいくつか表示されています。
一番上のセルは、水平セルコレクションです。2つのサブセルがあります。左側のサブセルは、「event」という単語を含む定数セルです。ここでは何も新しいことはありません。新しい要素は、refノードセルである2番目のセルです。refノードセルはrefノードリストセルに似ていますが、参照されるリンクが単一の値である場合に使用されます。ここでは、イベントタイプの場合です。
refノードセル自体には2つの部分があります。1つ目は、たどるリンクを示します。この場合は「type」です。2つ目は、ターゲットのどのプロパティを表示するかを示します。これはオプションの部分です。省略した場合、イベントタイプは通常のエディターを使用してレンダリングされます。ここでは、それを行うのではなく、単一のプロパティ、つまりタイプの名前をレンダリングしたいことを示しています。
では、投稿ルールのエディタ定義を見てみましょう。サンプルプラン(図1)では、エディタにルールの有効日とその詳細が表示されています。こちらがエディタ定義です。

図20:投稿ルールの時間属性エディタ
今回は、ルートセルは3つのサブセルを持つ水平セルコレクションです。日付用の参照ノードセル、":" の定数セル、そして投稿ルール自体用の別の参照ノードセルです。日付と投稿ルールはどちらも独自のエディタでレンダリングされます。
最後に紹介するエディタは、投稿ルールエディタです。

図21:投稿ルールエディタ
そろそろお馴染みになってきたと思います。ルートは、サブセルとして2つの水平セルコレクションを持つ垂直セルコレクションです。上部のセルには定数 "amount:" と式用の参照ノードがあります。式は、式言語の一部である式エディタによってレンダリングされます。下部のセルには定数 "account:" とアカウント用の参照ノードがあり、アカウントの名前プロパティが表示されます。
このようにテキストでエディタを記述するのは煩雑です。ある時点で、エディタの使用法のスクリーンキャストの方が分かりやすいかもしれません。エディタエディタは、少し慣れるのに時間がかかります。これは partly because I'm not used to defining editors, partly its because more work is needed to make the editor editor usable. This is new territory so JetBrains is still learning how this kind of thing should work.(私がエディタの定義に慣れていないことと、エディタエディタを使いやすくするためにはもっと作業が必要なことが原因です。これは新しい領域なので、JetBrainsはまだこのようなものがどのように機能すべきかを学習している段階です。)
重要なのは、最終的なプランエディタと同じくらいクリーンなエディタを定義するためには、柔軟性が必要だということです。この柔軟性を提供するために、複雑なエディタエディタが必要になります。使い勝手を良くするためにできることはたくさんあると思いますが、言語に適したエディタを定義するには、まだ多少の労力が必要になるでしょう。しかし、エディタはDSLの他の要素と緊密に統合されているため、比較的簡単に実験を行い、エディタ定義を変更して最適なエディタを探求することができます。
メイン編集ウィンドウとインスペクタの相互作用は、エディタ言語などのより複雑なDSLのエディタに関する別のポイントを明らかにしています。単一の投影ですべての編集を行おうとするのではなく、異なるものを表示する複数の投影を使用するのが最善の方法です。ここでは、メインエディタペインにエディタの全体構造が表示され、インスペクタに多くの詳細が表示されています。エディタを設計する際には、異なるペイン間で異なる要素を移動できます。
この場合、セルの階層を表示する3番目のペインは、インスペクタとWYSIWYG的なメインエディタペインを補完する有用な3番目の投影を提供するでしょう。
ジェネレータの定義
3つ組の最後の部分は、ジェネレータを作成することです。この場合、現在使用しているフレームワークを使用して適切なオブジェクトを作成するJavaクラスを生成します。このプランビルダークラスは、DSLを使用して定義した各プランのサービス契約クラスのインスタンスを作成します。
生成されるコードは、前に見たJavaの同等のコードとは少し異なります。これは、計算式を処理する方法が異なるためです。純粋なJavaバージョンでは、パラメータ化されていますが、限定された式クラスを使用して式を設定しました。このバージョンでは、式は式言語によって提供されます。
ジェネレータ定義の編集ペインの投影を以下に示します。

図22:生成の定義
生成されるコードは次のとおりです。(Webページのフォーマットに合わせて改行を追加しました。)
package postingrules; /*Generated by MPS*/ import postingrules.AgreementRegistry; import postingrules.ServiceAgreement; import postingrules.EventType; import postingrules.AccountType; import jetbrains.mps.formulaLanguage.api.MultiplyOperation; import jetbrains.mps.formulaLanguage.api.DoubleConstant; import jetbrains.mps.formulaLanguage.api.IfFunction; import formulaAdapter.*; import mf.*; public class AgreementRegistryBuilder { public void setUp(AgreementRegistry registry) { registry.register("regular", this.setUpRegular()); registry.register("lowPay", this.setUpLowPay()); } public ServiceAgreement setUpRegular() { ServiceAgreement result = new ServiceAgreement(); result.registerValue("BASE_RATE"); result.setValue("BASE_RATE", 10.0, MfDate.PAST); result.setValue("BASE_RATE", 12.0, new MfDate(1999, 12, 1)); result.addPostingRule( EventType.USAGE, new PostingRule_Formula(AccountType.BASE_USAGE, true, new MoneyAdapter(new MultiplyOperation( new ValueDouble("BASE_RATE"), new UsageDouble()), Currency.USD)), new MfDate(1999, 10, 1)); result.addPostingRule( EventType.SERVICE_CALL, new PostingRule_Formula(AccountType.SERVICE, true, new MoneyAddOperation( new MoneyMultiplyOperation(new FeeMoney(), new DoubleConstant(0.5)), new MoneyConstant(10.0, Currency.USD))), new MfDate(1999, 10, 1)); result.addPostingRule( EventType.SERVICE_CALL, new PostingRule_Formula(AccountType.SERVICE, true, new MoneyAddOperation( new MoneyMultiplyOperation(new FeeMoney(), new DoubleConstant(0.5)), new MoneyConstant(15.0, Currency.USD))), new MfDate(1999, 12, 1)); result.addPostingRule( EventType.TAX, new PostingRule_Formula(AccountType.TAX, false, new MoneyMultiplyOperation(new FeeMoney(), new DoubleConstant(0.055))), new MfDate(1999, 10, 1)); return result; } public ServiceAgreement setUpLowPay() { ServiceAgreement result = new ServiceAgreement(); result.registerValue("BASE_RATE"); result.registerValue("REDUCED_RATE"); result.registerValue("CAP"); result.setValue("BASE_RATE", 10.0, MfDate.PAST); result.setValue("REDUCED_RATE", 5.0, MfDate.PAST); result.setValue("CAP", new Quantity(50.0, Unit.KWH), MfDate.PAST); result.setValue("CAP", new Quantity(60.0, Unit.KWH), new MfDate(1999, 12, 1)); result.addPostingRule( EventType.USAGE, new PostingRule_Formula(AccountType.BASE_USAGE, true, new IfFunction<Money>( new QuantityGreaterThenOperation(new UsageQuantity(), new ValueQuantity("CAP")), new MoneyAdapter( new MultiplyOperation(new ValueDouble("BASE_RATE"), new UsageDouble()), Currency.USD), new MoneyAdapter( new MultiplyOperation(new ValueDouble("REDUCED_RATE"), new UsageDouble()), Currency.USD))), new MfDate(1999, 10, 1)); result.addPostingRule( EventType.SERVICE_CALL, new PostingRule_Formula(AccountType.SERVICE, true, new MoneyConstant(10.0, Currency.USD)), new MfDate(1999, 10, 1)); result.addPostingRule( EventType.TAX, new PostingRule_Formula(AccountType.TAX, false, new MoneyMultiplyOperation(new FeeMoney(), new DoubleConstant(0.055))), new MfDate(1999, 10, 1)); return result; } }
いつものように、生成の一部をすべてではなく、いくつか選んで説明します。特に、式から生成されたコードは、かなり醜いインタプリタ式です。これはクリーンアップする必要があり、近い将来に実現したいと考えています。
他のテンプレート言語と同様に、MPSのジェネレータ言語では、パラメータ参照を使用してテンプレート形式でクラスを記述できます。言語ワークベンチとの大きな違いの1つは、投影エディタを使用してテンプレートを定義することです。そのため、Java構文を理解し、この情報を使用してテンプレート生成を支援する、Javaクラス生成用の投影エディタを作成できます。ここでは、ジェネレータエディタが、Javaプログラムで見られるさまざまな種類の要素のマーカーを提供していることがわかります。これはメソッドのみを持っているため、他のものは使用されていません。
MPSのジェネレータ言語は、2種類のパラメータ参照を使用します。プロパティマクロ(`$`でマーク)とノードマクロ(`$$`)です。プロパティマクロは抽象表現を照会し、テンプレート出力に挿入する文字列を返します。ノードマクロは抽象表現を照会し、さらに処理するためにノードを返します。通常、他のテンプレートシステムのループに相当するものを処理するためにノードマクロを使用します。
どちらのタイプのマクロも、JavaのサポートクラスのJavaメソッドによって実装されます。MPSチームは、将来的にはJavaを生成のための抽象構文を照会するように設計されたDSLに置き換えたいと考えていますが、現時点ではJavaコードを使用しています。
プロパティマクロは、`$[_registryBuilder_]` のような参照で表示されます。 `$` を選択すると、インスペクタでマクロによってどの Java メソッドが呼び出されるかを確認できます。

図23:Javaプロパティマクロへのリンク
MPSとJetBrainsのIntelliJ Java IDEの統合により、従来のIntelliJの <CTRL>-B を押して、Javaのマクロ定義に移動できます。
public static String propertyMacro_RegistryBuilder_ClassName(SemanticNode sourceNode, SemanticNode templateNode, PropertyDeclaration property, ITemplateGenerator generator) { return NameUtil.capitalize(generator.getSourceModel().getName()) + "RegistryBuilder"; }
ご覧のとおり、これは非常に単純なメソッドです。基本的に、作業中のモデルの名前と "RegistryBuilder" を連結してクラス名を合成するだけです。
この種のことは、生成されたコードに挿入するさまざまな文字列を合成することを可能にします。メソッド内では、抽象表現のさまざまな部分にアクセスできます。契約DSLとジェネレータDSLの両方です。
- sourceNode は、ソース言語の現在のノードです。この場合は、契約言語です。
- templateNode は、ジェネレータ言語の現在のノードです。この場合は、ビルダーのジェネレータ定義の現在のノードです。
- property は、マクロを適用している現在のプロパティです。
- property declaration は、このプロパティの宣言(スキーマから)です。
- generator は、現在のジェネレータインスタンスです。これは、現在のプロジェクトとモデルにリンクしています。
エディタの投影では、このパラメータ参照にはエディタの投影に名前があることがわかります。`_registryBuilder_` です。これは、エディタで複数の参照を許可するラベルです。この例は、後でテンプレートに示します。各契約は、別々のメソッド(`setUpRegular()`と`setUpLowPay()`)で構築されます。これらは、全体的なセットアップメソッドから呼び出す必要があります。そのため、これらのメソッドの名前は、メソッド定義と呼び出しの両方から参照する必要があります。ラベル `_setUp_plan_` を使用すると、それが可能になります。図22 では、`setUp` メソッドの繰り返し行内と、各メソッド用に生成されたテンプレートのメソッド名の両方でラベルが表示されています。実際、テンプレートエディタは投影エディタであるため、必要なときにこれらのラベルを選択するためのポップアップメニューを取得できます。エディタは、Javaプログラムのテンプレートを作成していることを認識しているため、この情報を使用して投影の編集を支援できます。
確認できる2番目の種類のマクロは、ノードマクロです。ノードマクロは、エディタに `$$ [追加のテンプレートコード]` として表示されます。括弧で囲まれたテンプレートコードは、マクロから返された各ノードに適用されます。契約作成メソッドの画面を以下に示します。

図24:ノードマクロへのリンク
これは、次の Java コードにリンクしています。
public static List<SemanticNode> templateSourceQuery_Plans(SemanticNode parentSourceNode, ITemplateGenerator generator) { List<SemanticNode> list = new LinkedList<SemanticNode>(); List<SemanticNode> roots = generator.getSourceModel().getRoots(); for (SemanticNode node : roots) { if (node instanceof Plan) { list.add(node); } } return list; }
ご覧のとおり、プロパティマクロは文字列を返すのに対し、ノードクエリはセマンティックノードのリストを返します。この場合、抽象表現のルートをウォークスルーし、そこに存在するすべてのプランノードを返します。ジェネレータは、各プランについて、囲まれた定義済みコードを生成します。(このようにして、VTLのループディレクティブのように動作します)。
ノードマクロ内では、囲まれたテンプレートは、マクロによって返される各ノードに対して1回適用されます。sourceNode引数をそのノードに設定します。そのため、後でメソッドに名前を付けるときに、次のJavaビットを使用できます。
public static String propertyMacro_Plan_SetUpMethod_Name(SemanticNode sourceNode, SemanticNode templateNode, PropertyDeclaration property, ITemplateGenerator generator) { Plan plan = (Plan) sourceNode; return "setUp" + plan.getName(); }
ソースノードはプランノードであり、プランのスキーマには文字列である名前があるため、名前を使用してメソッドの名前を生成できます。
テンプレートの残りの部分は、基本的に同じように機能します。現在のソースノードからプロパティを取得するか、ノードマクロを使用して別のノードを取得して作業します。
MPSでテンプレートを定義することは、従来のテンプレートベースのアプローチと非常によく似ています。繰り返しますが、クエリを実行して結果を生成されたコードに挿入する抽象表現があります。この例からわかる主な違いは、さまざまな種類のテンプレート出力(この場合はJavaクラス)の投影エディタを構築できることです。
まとめ
この例が、まだ多少未完成ではありますが、言語ワークベンチを使用するのがどのようなものかを感じさせてくれることを願っています。多くの点で、この例は、まだ従来のテキストDSLに似ているという点で、ほとんどが不足しています。言語ワークベンチで示唆したように、本当に興味深いDSLは実際にはかなり異なっていると思います。しかし、この作業の性質の一部は、まだそれらがどのように見えるかを実際にはわからないということです。
重要な改訂
2005年6月12日:初版