制御の逆転 〜 OchaCaml と限定継続でイベンドハンドラをダイレクトスタイルで書く
昔、こういう記事を書いた:
OchaCamlで限定継続/answer type modificationを勉強する - keigoiの日記
もっと shift/reset で何か身の回りの例を書いてみたい。そこで限定継続を使って、イベントハンドラをコールバックではなくダイレクトスタイルで書けるようにする、ということをやってみる。
ユースケース
例がとても古くて恐縮だが、たとえば古きよきショッピングカートの web アプリを作るときは、普通はページのルーティングを書いて、クリックの度に発行される GET か POST リクエストに反応するイベントドリブンで構成することになる:
/* /item_list アイテム一覧ページ */ Page onItemList(); /* /confirm 注文の確認ページ */ Page onConfirm(Cart); /* /thankyou 支払い完了ページ */ Page onThankyou()
これはよくあるパターンだけど、プログラムの制御の流れは「ぶつ切り」になり見辛いし、ページ遷移の流れもわからない…と言い張ることもできる。
しかし、もし限定継続があったならば、ページ遷移の流れをプログラムの制御フローにより直接的に書き下すことができる:
var cart = []; while(true) { cart, checkout = show_item_list(); if (checkout) { ok = show_confirm_page(); if (ok) { break; } } } show_thankyou_page();
プログラム
int 型のイベント発生源があるとして、そのハンドラをコールバックではなくダイレクトスタイルで書いてみよう。
まずはライブラリコード:
(* int 型の穴をもつ継続 *) type 'a t = Fini | Cont of (int / 'a -> 'a t / 'a) let cont = ref Fini (* 関数 body をイベントハンドラに登録する関数 *) let register body = cont := reset (fun () -> body (fun () -> shift (fun k -> Cont k)) ) (* イベントハンドラ *) let event x = match !cont with | Fini -> failwith "finished" | Cont k -> cont := k x
実際にダイレクトスタイルでイベントハンドラを書いてみる:
(* int型のイベントを 2回待ってその和を表示する *) register (fun f -> let x = f () in let y = f () in print_int (x+y); Fini);;
イベント発生をシミュレートしてみましょう。 10, 20 が順番に起こったとする。
# event 10;; - : unit = () # event 20;; 30- : unit = ()
見事、ダイレクトスタイルで書いたイベントハンドラが動き、和が表示されました。やったぜ。
これくらい Python の yield とかでも出来てしまうけれど、そのあたりはご愛嬌。
もっと専門的な例を見てみたい人は Continuation Workshop 2011 Tutorial を参照しましょう。
OchaCaml は研究用の言語ではあるものの、 Hindley-Milner 型推論と answer type modification があるので安全かつ自由度の高いプログラムが書けるのだ。