このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

2010年末に再び考える、Catyスキーマとユーザーインターフェース

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としてグループ化されます。

ラベルのデフォルトは次のとおり。

  1. オブジェクトのプロパティのラベルはプロパティ名
  2. 列挙型(値のユニオン型)の各値のラベルは、その値の文字列表現
  3. その他のデフォルトのラベルは空

このルールで、先の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フォームを書き下すなんて作業はウンザリの典型ですね。

この作業をコンピュータができない理由は(それがあるとすれば)、型定義がコンピュータが扱えるデータになってないから; それだけです。型や手続きや関係やらが、データになっていれば、通常は人間がやるような概念的(に見える)操作もコンピュータに任せることができます。つまりは、現実の世界や概念の世界の事物をコンピュータのなかに押し込めればいいのです。コンピュータのなかの世界には、当のコンピュータ(プログラム)自身も入れ子に埋め込まれることになります。これがメタ循環構造です。

メタ循環構造を実現すれば、コンピュータがやれる作業が増えて、僕らは楽できます。

*1:列挙値の値に対するアノテーションを除けば、現状のCatyでもパーズできます。

*2:圏論の言葉で言えば、閉構造です。内部ホム対象、あるいは指数(ベキ)が閉構造を与えます。