layout | title | permalink |
---|---|---|
page |
Why donut? |
/why/ |
何のために時間操作のできるプログラミング言語が必要なのでしょうか?その前に、まずはちょっと根本的な話をしましょう。
世の中には様々な種類のソフトウェアがありますが、とても大きなくくりの一つとして、ユーザーとのやり取りがメインのソフトウェアがあります。ちょっと漠然としすぎていると思いますので、いくつか例を上げましょう。
例えばAmazon.comなら、ユーザーが「ユーザー登録」から個人情報を送ったり、商品を検索したり、商品をカートに入れたり注文したり、といった個別のページでの小さなアクションを順序にしたがってうまくつなぎあわせて、「ユーザー登録→商品物色→カートに入れる→注文」という、「ショッピングサイト」という大きなウェブサイトとしての機能を実現しています。
例えばテキストエディタなら、キーボードのキーを押してテキストを編集したり、保存メニューを選択したりといったユーザからの小さなアクションに対して、画面上のテキストを更新したり、保存ダイアログを出して保存する場所を聞いたり、保存できたらメッセージを表示したり、といった結果のフィードバックを返す、というのを場面ごとに秩序をもって組み合わせることで、「テキストを開いて編集する」という、大きなエディタとしての機能を実現しています。
ロールプレイングゲームでは、町人のアイコンに向かってボタンを押すとメッセージが表示される、フィールドと呼ばれる座標空間を移動している時に戦闘モードに移行する、戦闘モードで倒したりやられたりした結果、町人のメッセージが変わっていく、などの個別の小さな「行動→結果」の積み重ねで、壮大な「世界を救う」という物語世界を表現しています。
さて、これらのユーザーの小さなアクションの間には、大きなアプリケーションやゲームとしてちゃんと機能するように、前提条件や制約があります。どれも、当たり前のものばかりです。
- Amazonでは、ユーザーの「マイページ」や「注文履歴」は認証ページで認証してからでないとアクセスできません。
- テキストエディタでは、ファイルを開かないと「上書き保存」は使えません。
- 魔王を倒すための最強装備を手に入れるには、魔物を倒してオリハルコンを手に入れて無ければ作ってもらえません。
- ビアンカとフローラのどちらを選んだかで、ストーリーが違います。
- ファイルを開いていないのに既存のファイルに上書きするバグったテキストエディタ
- ログインしてないのに注文履歴が見れるセキュリティも何もない悲惨なショッピングサイト
- 死んだ仲間が戦闘に加わるシュールなRPG(あっ、よくある…)
これらの制約条件を記述するために、色々な方法が考えられてきました。
とても一般的な方法でしょう。例えばウェブアプリであれば、ユーザーセッションの中にisLoggedInのような変数を用意しておき、認証されたユーザーでないと特定のページにはアクセスできないように制御できます。
if !@session.isLoggedIn then
redirect_to :action => "new_user" #ユーザー登録画面に誘導
end
この方法はとても簡単で、柔軟さもありますが、すぐに複雑になってしまいます。例えば、次のように分岐したページ遷移に対して適切なフラグを間違いなく、一発で作成できますか?
よくRPGでは「死んだ仲間が生き返る」「キーアイテムを持ってないのに次に進めた」「逆にアイテムがあるはずなのに扉が開かないので詰んだ」等のバグをよく見かけると思いますが、それらのバグはこのフラグ管理に失敗した結果発生します。
GUIアプリケーションでは一般的な手法です。最近はウェブアプリケーションでも、node.jsなどでの採用例があります。それぞれのアクションに対応したコードをコールバックとして登録することでアプリケーションを制御していく方法です。この方法も簡単なのですが、
- コールバックに書いた物理的にぶつ切りになったコードが実行されていくため、流れが(純粋なフラグに比べると大分いいけど)わかりづらい。
- 登録されるコールバックが実質的にフラグであるため、やはりこんがらがる
これまででメジャーな制御方法のフラグやコールバック方式を見てきましたが、ほんとにうんざりします…それに引き換え、Hello Worldを書いて喜んでいた、あの頃は良かった!Railsアプリではユーザーのログインのために幾つもコントローラにアクションを作成しなければなら ない所が、あの頃のコンソールアプリならこれだけ直感的に書けるのでした。
while true do
puts "パスワードを入力して下さい!"
passwd = gets
if passwd == "正しいパスワード" then
puts "認証完了"
break
end
puts "間違ったパスワードです"
end
puts "こんにちは、正しいユーザーさん!"
この方法は安直ですが、思えば良い所がたくさんあります。
- Aの後にBが実行されるという順序構造が自然に表現され、処理が間に割り込まれない事が保証される。
- フラグの依存関係を設定するよりも、「正しいパスワードを入れないと次に進めない」ことが自然に記述できる。
- 処理がひとつのソース上に固まっており、複数のコントローラやコールバックをいくつも追いかけなくても処理全体が把握できる。バグの発見もしやすい。
継続はウェブアプリケーションフレームワークのKahuaやSeaSideで使われている手法です。プログラムの「継続=現時点からの残りのプログラム処理」保存しておいて、任意の時点から再開させることができ、あたかも一つながりのプログラムとして書くことができます。例えば、先程のSeaSideのサンプルプログラムを見てみましょう。
go
"ログイン成功するまで繰り返し"
[self call: MyLoginComponent new]
whileFalse: [self call: (MyInformComponent new message: 'パスワードが違いますよ?')].
"ログイン成功"
self call: (MyInformComponent new message: 'ようこそ').
"投票の開始"
self call: MyVoteComponent new
どうでしょう?Smalltalkなのであまり親しみの無い構文だと思われたかもしれませんが、かなりコンソールアプリに近い見かけをしていると思いませんか?
また継続は実行後も取っておけるため、バックで戻った後に戻った時点から処理を再開させるような事もできてしまいます。
しかし、この方法にもいくつか難があります。バックしてその時点から処理を再開することはできますが、データベースなどへの副作用は自分で管理しないと、注文を確定した後にカートの中身を編集できるようになってしまったりしてしまいます。この副作用のハンドリングが難しいためか、GUIアプリケーションでの採用例は存在しないようです。
Haskellなどの関数型言語で使われる手法です。Yampaなどのライブラリが有名で、純粋関数型を守りつつGUIアプリなどを記述することができます。しかし…
- やはり関数型スタイルの記述が必要で、難しいです。
- アカデミックな世界からまだあまり出てきていないようです
- 継続スタイルのように戻して再開させることは出来ません。
今までの問題点に対して、この「時を司るプログラミング言語」では、
- 継続型フレームワークのように、実行を任意の時点に戻せます。
- 「フラグ」のように、セーブデータに書き出してあとで読み出せます。
- 戻した後何もしていない場合、戻したことを取り消して、最新の状態に戻すこともできます。
- 外部に起こした副作用について、「どうやって戻すか」という「反副作用」のようなものを登録することでちゃんと管理します。
- 手続き型の「いつものスタイル」で書けます。