1年半ほど前、「JSONスキーマとユーザーインターフェース」とか「JSONスキーマと列挙型」において、ランチの注文フォームを素材にスキーマとユーザーインターフェースの問題を考えてました。今、2010年末ならこの問題をどう扱うか?
まず、データ型の定義は次のようになります。
/** * ランチの注文 */ type LunchOrder = { /** 会員識別情報 */ "ident" : { "name" : string, "memberNum" : integer }, /** 今日の注文 */ "order" : { "mainDish" : ( "special"| "fish"| "meat" ), "drink" : ( "teaHot"| "teaIce"| "coffeeHot"| "coffeeIce" ), "dessert" : ( "pudding"| "strawberryShortcake"| "bavarianCream"| "gateauChocolat" ) } };
この定義は、今や擬似コードではなくて、Caty内でちゃんと動きます。
caty:test> lunch-order.caty { "ident":{ "memberNum":1234, "name":"m-hiyama" }, "order":{ "dessert":"bavarianCream", "drink":"coffeeHot", "mainDish":"special" } } caty:test> lunch-order.caty | validate LunchOrder @OK { "ident":{ "memberNum":1234, "name":"m-hiyama" }, "order":{ "dessert":"bavarianCream", "drink":"coffeeHot", "mainDish":"special" } } caty:test>
問題は単にデータ型だけではありません。ユーザーインターフェースを構成するHTMLフォームの生成に必要な情報を、スキーマ内に埋め込もうとしていたのです。とりあえず、フォームの入力用部品(フォームコントロール)を復習しておきます。
要素 | 属性 | 略称 | 説明 |
---|---|---|---|
input | type="text" | text | 単一行テキストの入力 |
input | type="password" | password | パスワードの入力 |
input | type="radio" | radio | ラジオボタン |
input | type="checkbox" | checkbox | チェックボックス |
textarea | textarea | 複数行のテキスト入力 | |
select | single-select | セレクトボックス | |
select | multiple | multiple-select | 複数選択セレクトボックス |
各フォームコントロールからどんなデータ型を入力するかをまとめると:
コントロール | データ型 |
---|---|
text | string, number |
password | string |
radio | enum(列挙型), boolean |
checkbox | bag(バッグ型) |
textarea | string |
single-select | enum |
multiple-select | bag |
逆の順序でまとめれば:
データ型 | コントロール |
---|---|
number | text |
string | text, password, textarea |
boolean | radio |
enum | radio, single-select |
bag | checkbox, multiple-select |
この他に、フォームコントロールをグループ化するfieldset、フォーム全体を表すformがあります。
どのフォームコントロールを使うかを、uiというアノテーションで注記することにします。Catyスキーマのアノテーション構文では、@[ui("text")] (textコントロールを使う)のようになります。それと、UIで使うラベル文字列が必要なので、@[label("お名前")] のようにして示します。
データ型に対するデフォルトのコントロールは、上の表にある最初のコントロールとします。例えば、string型はデフォルトでtextコントロールを使います。トップレベルのオブジェクトはformになり、入れ子のオブジェクトはfieldsetとしてグループ化されます。
ラベルのデフォルトは次のとおり。
- オブジェクトのプロパティのラベルはプロパティ名
- 列挙型(値のユニオン型)の各値のラベルは、その値の文字列表現
- その他のデフォルトのラベルは空
このルールで、先のLunchOrder型からHTMLフォームを作ると次のようになります。
<form method="post" action="lunchOrder.cgi"> <fieldset id="ident"><legend>ident</legend> <div class="textField" id="ident.name"> name:<input type="text" name="ident.name"> </div> <div class="textField" id="ident.memberId"> memberId:<input type="text" name="ident.memberId"> </div> </fieldset> <fieldset id="order"><legend>order</legend> <div class="checkboxGroup" id="order.mainDish"> mainDish: <input type="radio" name="order.mainDish" value="special" /> special <input type="radio" name="order.mainDish" value="fish" /> fish <input type="radio" name="order.mainDish" value="meat" /> meat </div> <div class="radioGroup" id="order.drink"> drink: <input type="radio" name="order.drink" value="teaHot" /> teaHot <input type="radio" name="order.drink" value="teaIce" /> teaIce <input type="radio" name="order.drink" value="coffeeHot" /> coffeeHot <input type="radio" name="order.drink" value="coffeeIce" /> coffeeIce </div> <div class="checkboxGroup" id="order.dessert"> dessert: <input type="checkbox" name="order.dessert" value="pudding" /> pudding <input type="checkbox" name="order.dessert" value="strawberryShortcake" /> strawberryShortcake <input type="checkbox" name="order.dessert" value="bavarianCream" /> bavarianCream <input type="checkbox" name="order.dessert" value="gateauChocolat" /> gateauChocolat </div> </fieldset> <p><input disabled type="submit" value="送信する"></p> </form>
表示はこんな感じ。
ラベルを日本語にしたいなら、次のような @[label]アノテーションを付けることになります*1。@[checked]は、checkedまたはselectedを付けることを示すアノテーションです。
type LunchOrder = { @[label("ランチ会員情報")] "ident" : { @[label("お名前")] "name" : string, @[label("会員番号")] "memberNum" : integer }, @[label("本日のご注文")] "order" : { @[label("メイン")] "mainDish" : ( @[label("日替わり")] @[checked] "special"| @[label("魚")] "fish"| @[label("肉")] "meat" ), @[label("お飲み物")] "drink" : ( @[label("紅茶 (ホット)")] "teaHot"| @[label("紅茶 (アイス)")] "teaIce"| @[label("コーヒー (ホット)")] @[checked] "coffeeHot"| @[label("コーヒー (アイス)")] "coffeeIce" ), @[label("デザート")] "dessert" : ( @[label("プリン")] "pudding"| @[label("苺ショートケーキ")] "strawberryShortcake"| @[label("ババロア")] "bavarianCream"| @[label("ガトーショコラ")] "gateauChocolat" ) } };
これだけ@[label]が頻出するなら、「アノテーションに単に文字列を書いたらラベルになる」というショートハンドの約束をしたほうがいいかもしれません。このショートハンドを使うと、少しスッキリします。
type LunchOrder = { @["ランチ会員情報"] "ident" : { @["お名前"] "name" : string, @["会員番号"] "memberNum" : integer }, @["本日のご注文"] "order" : { @["メイン"] "mainDish" : ( @["日替わり", checked] "special"| @["魚"] "fish"| @["肉"] "meat" ), @["お飲み物"] "drink" : ( @["紅茶 (ホット)"] "teaHot"| @["紅茶 (アイス)"] "teaIce"| @["コーヒー (ホット)", checked] "coffeeHot"| @["コーヒー (アイス)"] "coffeeIce" ), @["デザート"] "dessert" : ( @["プリン"] "pudding"| @["苺ショートケーキ"] "strawberryShortcake"| @["ババロア"] "bavarianCream"| @["ガトーショコラ"] "gateauChocolat" ) } };
このアノテーション付きスキーマから、次のようなフォームが生成できるはずです。
<form method="post" action="lunchOrder.cgi"> <fieldset id="ident"><legend>ランチ会員情報</legend> <div class="textField" id="ident.name"> お名前:<input type="text" name="ident.name"> </div> <div class="textField" id="ident.memberId"> 会員番号:<input type="text" name="ident.memberId"> </div> </fieldset> <fieldset id="order"><legend>本日のご注文</legend> <div class="checkboxGroup" id="order.mainDish"> メイン: <input checked type="radio" name="order.mainDish" value="special" /> 日替わり <input type="radio" name="order.mainDish" value="fish" /> 魚 <input type="radio" name="order.mainDish" value="meat" /> 肉 </div> <div class="radioGroup" id="order.drink"> お飲み物: <input type="radio" name="order.drink" value="teaHot" /> 紅茶 (ホット) <input type="radio" name="order.drink" value="teaIce" /> 紅茶 (アイス) <input checked type="radio" name="order.drink" value="coffeeHot" /> コーヒー (ホット) <input type="radio" name="order.drink" value="coffeeIce" /> コーヒー (アイス) </div> <div class="checkboxGroup" id="order.dessert"> デザート: <input type="checkbox" name="order.dessert" value="pudding" /> プリン <input type="checkbox" name="order.dessert" value="strawberryShortcake" /> 苺ショートケーキ <input type="checkbox" name="order.dessert" value="bavarianCream" /> ババロア <input type="checkbox" name="order.dessert" value="gateauChocolat" /> ガトーショコラ </div> </fieldset> <p><input disabled type="submit" value="送信する"></p> </form>
表示は次のようでしょう。
型定義からフォームを生成することは、それだけを目的にすればさほど難しくはないでしょうが、まっとうな型システム/アノテーションシステムの副産物として導出するのはけっこうな手間です。例えば、アノテーションはターゲット構文構成素の直前に出現しますが、実用性からは次の位置にアノテーションを許す必要があります。
- オブジェクト型の直前
- 配列型の直前
- 列挙型/ユニオン型の直前
- オブジェクト型のプロパティ(property)の直前
- 配列型の項目(item)の直前
- 列挙型/ユニオン型の選択肢(choice, altenative)の直前
しかも、アノテーションを付与された型定義をデータとして扱える必要があります。「アノテーションを含む型」のレイフィケーション(reification)、あるいはメタ循環(メタ巡回)構造です*2。Catyのメタ循環構造はまだ不完全なのですが、少しずつ整備しています。そのうち、副産物としてランチ注文フォームが作れるようになるでしょう。
ついでに、僕がなんでメタ循環構造に拘るかをチョット:
コンピュータが扱えない対象物に対する操作は、人間がやることになります。その作業が十分に知的で、コンピュータでは出来そうにない事ならやる意義も価値もあるでしょう。しかし、さして知的でもない作業を人手でやるのはウンザリ。型の定義を目視で見て、対応するHTMLフォームを書き下すなんて作業はウンザリの典型ですね。
この作業をコンピュータができない理由は(それがあるとすれば)、型定義がコンピュータが扱えるデータになってないから; それだけです。型や手続きや関係やらが、データになっていれば、通常は人間がやるような概念的(に見える)操作もコンピュータに任せることができます。つまりは、現実の世界や概念の世界の事物をコンピュータのなかに押し込めればいいのです。コンピュータのなかの世界には、当のコンピュータ(プログラム)自身も入れ子に埋め込まれることになります。これがメタ循環構造です。
メタ循環構造を実現すれば、コンピュータがやれる作業が増えて、僕らは楽できます。