Kuwataさんの昨日の日記 http://return0.dyndns.org/log/2009/10/06#s_1 を読んで僕はゲラゲラと笑ってしまった。内容的には、Catyスクリプトの分岐処理のマジメな話で、これで大笑いするのは僕だけだろう。
まず、前後の事情を説明すると、昨日Kuwataさんと打ち合わせて、Catyスクリプトの分岐処理をどうすべきかが話題になった。例を示そう。Kuwataさんが出した例と本質的に同じ:
- ユーザーがログインしているかどうかをチェックする。
- トークン(ワンタイムチケット)をチェックする。
- フォームデータが適切かどうかをチェックする。
- フォームデータを処理する(ここではエラーチェック不要)。
- なにかがまずいなら、適切なページにリダイレクト、またはフォーワードする。
if (logged_in()) {
if (has_token(form_data)) {
if (is_valid(form_data) {
フォーム処理;
} else {
再入力ページ;
}
} else {
エラーページ;
} else {
ログインページ;
}
Catyスクリプトでは次のようになる。logged_in, has_token, is_valid は、OKまたはNG というタグが付いたデータを出力して、when構文で分岐する。=>> はタグを削り落として次の処理に渡す記号(=> ならそのままタグ付きで次の処理に渡す)。
logged_in |
when {
OK =>> has_token |
when {
OK =>> is_valid |
when {
// フォーム処理
OK =>> process_form | print /form/ok.html,
// 再入力ページ
NG =>> print /form/retry.html
},
// エラーページ
NG =>> redirect /form/notoken.html
},
// ログインページ
NG =>> redirect /login.html
}
Catyスクリプトの構文は独特だが、他のプログラミング言語とやることは同じだし、記述量も変わらない。良くもないし悪くもない。だから、「これでいいだろう」というのが僕の意見。
kuwataさんは「これでいいだろう」とは思ってない。いや、少なくとも打ち合わせしていた時はそう思っていなかった。にもかかわらず、Kuwata日記の最後は「まあいいかなという気もする」だったので可笑しかったのだ。「いやもう痛いの痛くないのって、… 痛くなかった」というギャグみたいな可笑しさかな。
さらに笑える要因は、Kuwataさんが「まあいいかなという気もする」と書いていた頃に僕は、「これはやっぱりまずいかもな」と思っていたこと。
今のCatyの(暫定的な)実装だと、なんと次のようなストレートなパイプラインを書くだけでエラー処理までやってくれる。
logged_in | has_token | is_valid | process_form | print /form/ok.html
フォームのバリデーションを明示的に書く必要もない(勝手に確実にやる)ので、実際はさらに短くて:
logged_in | has_token | process_form | print /form/ok.html
なかなかにマジカル*1なんだけど、困ったことに、ユーザーがエラー処理を変えられない。エラー処理のカスタマイズを設定ファイルに書くという手はある、次のように。
"errorHandle" : {
"404" : "redirect /errors/404.html",
"not_logged_in" : "redirect /login.html",
"invalid_form" : "print /form/retry.html",
...(省略)...
}
しかしこれでは、パイプラインごとに個別のエラー処理は書けない。不便だ。ダメだ。
エラー処理をユーザーが自由に書けるようにはしたい、けど、入れ子になった場合分けをスクリプトで書きたくないし、コマンド側が OK, NG のタグ付きデータを出力するのも鬱陶しい、というのがKuwataさんの意見。
この問題の対処として、Kuwataさんが出してきた提案は、ユーザーレベルでメタコマンド(高階コマンド)を書くことを許すことだった。メタコマンドとは、コマンド・パイプラインを引数に取れるコマンドのこと。現状では、メタコマンドはスクリプト言語の予約語として言語に組み込まれていて、インタプリタ自身が処理する。
メタコマンドの作成をユーザーに許すと、新しい制御構造をいくらでも追加できるので、スクリプト言語仕様そのものがカスタマイズ可能となる。これは、言語の機能とフレーバーをドラスチックに変えるもので、いくらなんでもやり過ぎだ。難易度と複雑さが増大する*2。却下。
コマンドを呼ぶたびに出力をチェックしては分岐を繰り返す方法は、一般論としてはマズイのは確かだ。以前、僕が批判的に取り上げたErlangのok/error方式(「Erlang実験室:ok/error方式はやっぱりダメなんだよ」)と同じやり方だからね。
Erlangの場合は、catch式という中途半端な例外処理(つうより、大域脱出とその飛び先)しかなかったので、ok/error方式が多用されていた。やっと最近になって、try式という洗練された例外処理機構が導入された経緯がある。
となると、「Catyスクリプトにも洗練された例外処理機構を」という話になりそうだが、僕は「要らない」と判断していた。Erlangは、百万行規模のフォールトトレラントシステムを書くような汎用言語*3である。それの例外処理がヘボなのは大問題だろう。が、Catyスクリプトはたいてい2,3行で終わってしまう。可読性やメンテナンス性を云々するのが大仰なのだ。
だがしかし、次の要件を満たす例外処理機構なら、導入に意義があるかも知れない。
最後の条件は、「タグ付きデータによる分岐と統合できる」だとさらに望ましい。これは、OK/NG のタグ方式で書かれたコマンドと例外方式で書かれたコマンドを統一的に扱えること。Erlangではそれが出来なかった。そのため、古いAPI関数は新しい例外処理機構で扱えないという弊害を生んだのだった。
今現在、「Catyスクリプトに例外がほんとに必要だ」とは思ってない。なくてもなんとかなる。だが、このての問題を考えるのに、僕は有利な位置にいる。Erlangの古いAPI関数をラップした経験から、タグ方式と例外方式の折り合いをつける方法は分かる。上に書いたその他の要件もクリアできそう。
ついでに言っておくと、Catyスクリプトの意味論は、デカルト分配圏の上で展開できる。例外処理機構は、ベースとなる分配圏上の直和スタンピングモナドのクライスリ圏で定式化できる*4。つまり、例外処理機構が意味論を破綻させる心配はない。
んで、実際の例外処理機構とその構文は次の機会に。