コンパイル時 vs. 実行時
要旨:
1.手続き的なコード、宣言的な記述
手続き型言語のコードでGUIフォームを表現すると、ウィジェット生成などの実行順序がコードに表れる。これは不要なものであり、邪魔だ。
GUIフォームは、手続き型言語のコードの外で、実行順序という概念のない宣言的な記述で表現されるべきだ。実例としては、XAMLがある。
2.コンパイル時、実行時
プログラミング言語のコード(Java、C#の)はコンパイル時に検査される。
コンパイル時の検査に、コード外の記述を含めたい場合がある。ASP.NETやXAMLではそれができる。
ASP.NETやXAMLでは、コード外記述のスキーマやコンパイル時検査の範囲が、仕様として与えられている。
コード外記述のスキーマやコンパイル時検査の範囲を、簡単に自作できるようにすると、いいDSL (Domain Specific Language) 環境になりそうだ。
3.宣言的記述のスキーマ
手続き的なものをコード外に記述する必要はあまりないので、コード外記述は宣言的になる。
コンパイル時検査の範囲、構造体、プリミティブ型(int、double、String)、型付きコレクション(List、Bag、Set、Map)を表現できるようなスキーマ言語が必要だ。
4.コンパイル時検査の未来
IDEでのコード補完によるミススペル回避だけなら、宣言されたトークンとマッチするだけで、だいたい用が足りる。コンパイル時検査の未来は暗いかもしれない。
Java SE 6が前提なら、Swingのフォームを作るにはNetBeansがお勧めだ。GUIエディタでウィジェットを並べて、生成されたコードをJava SE 6用に直す。これなら、かなり手のこんだフォームも楽勝で作れる。
このコードは、ウィジェットを手続き的に生成したり加えたりして、求める配置を描き出す。サンプルコードはこんな具合だ。
layout.setHorizontalGroup(layout.createParallelGroup(
GroupLayout.Alignment.LEADING).addGroup(
GroupLayout.Alignment.TRAILING,
layout.createSequentialGroup().addContainerGap(91,
Short.MAX_VALUE).addComponent(newButton)
.addContainerGap()).addComponent(jScrollPane,
GroupLayout.DEFAULT_SIZE, 216, Short.MAX_VALUE));
layout.setVerticalGroup(layout.createParallelGroup(
GroupLayout.Alignment.LEADING)
.addGroup(
GroupLayout.Alignment.TRAILING,
layout.createSequentialGroup().addComponent(
jScrollPane, GroupLayout.DEFAULT_SIZE, 200,
Short.MAX_VALUE).addPreferredGap(
LayoutStyle.ComponentPlacement.RELATED)
.addComponent(newButton).addContainerGap()));
ここでは何か間違ったことが起きている。
ウィジェットを画面に並べるときには、実行順序などという概念はそもそも存在してほしくない。紙に文字を印刷するとき、「左上の文字は右下の文字より先に印刷すべし」などと考えるだろうか。出来上がった印刷物にそんな指定は反映されない。結果と無関係なものを考えるはずもない。考えるはずのないことを指定させられる状況は、何か間違っている。
上のサンプルコードには、実行順序が存在しており、しかも重要だ。たとえばlayout.createSequentialGroupは、その戻り値へのaddContainerGapよりも先に実行されるし、そうでなければならない。こんな実行順序は、実装の都合だけで存在するものであり、ユーザの目からは隠すべき詳細だ。NetBeansでは実際、GUIエディタでこのコードを隠蔽している。だがJava SE 6だけで動かしたい場合、隠蔽は崩さざるをえず、実行順序がコード上に丸出しになる。
Windows Presentation FoundationのXAMLは、この問題への解答になっている。NetBeansは単体で2つの仕事(実行順序の隠蔽、GUIエディタ)をこなしているが、XAMLは実行順序の隠蔽をコンパイラに任せ、GUIエディタは宣言的記述を生成する。
XAML方式で問題は解決したかに思われる。だが、これは十分に一般的な解決とはいえない。私は今日、そのことに気づいた。
きっかけはプロパティファイルだ。
もしプロパティファイルのキーをスペルミスしたら、実行時にエラーになるまで、わからない(だからクラスロード時にエラーが出るようにするのだが、このへんのバッドノウハウは後日)。IDEのコード補完も働かない。猛烈にいらだたしい。プロパティファイルを規定しているのはJava言語なのだから、キーはコンパイル時に定数として書けるようにしてほしい。Messages.propertiesがありその中にBAR=fooがあれば、Messagesクラスが生成されBARというfinal Stringフィールドがある、という具合にだ。この機能を仮に「クラス化プロパティ」と呼ぶ。
(BARの値はMessagesクラスのインスタンス生成時に決定される。なぜクラスロード時でないかといえば、リソースバンドルに対応するためだ)
XAMLとクラス化プロパティ、どちらも、コンパイル時に利用できる宣言的記述だ。ただし相違点は多い。
表現力:XAMLはGUIフォームのような複雑な構造を表現する。クラス化プロパティが表現するのはfinal String型の変数の集合にすぎない
記述内容をバインドするタイミング:XAMLは記述内容全体がMSILへとコンパイルされる。つまり、XAMLはコンパイル時にすべてがバインドされる。クラス化プロパティでは、キー(BAR)はコンパイル時に決定されるが、その値(foo)はインスタンス生成時までバインドされない
まとめると、表現力ではXAMLが優れており、バインドを遅らせることではクラス化プロパティが優れている。
なぜXAMLはコンパイル時にバインドしてしまう設計になっているのか。
理由その1.GUIフォームを表現するという用途では、バインドを遅らせる必要はあまりない
理由その2.バインドを遅らせる部分を任意に指定できるようにするのが面倒くさい。記述内容全体をコンパイルしてしまう(XAML方式:コンパイル時)か、コンパイルには一切関与させない(通常のプロパティファイル方式:実行時)か、どちらかにするほうが簡単だ
……ここで力尽きた。各自要旨から適宜補完されたい。
Posted by hajime at 2007年04月28日 01:15