GoF in OCaml for Advent Calendar 2012 #7
OCaml Advent Calendar用の記事、第七弾目です。今日から振る舞いに関するパターンシリーズに突入です。最初はChain of Responsibilityパターンいってみましょう。
http://ja.wikipedia.org/wiki/Chain_of_Responsibility_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3
Chain of Responsibilityは特定のデータを処理の鎖に流し込む手法です。どのような順番で鎖を繋げるか、鎖の途中でデータの流れを止めるか止めないか、その辺りの変更で動的な処理の流れが構築できる訳です。
OCamlでChain of Responsibilityするなら(t -> t)の関数を合成するのが最も単純な戦略だと思います。途中で処理を破棄したい場合は、(t -> t option)の関数を使ってoption bindする手もあるでしょう。Wikipediaの例が単純な例なので、そちらに合わせたプログラムは下記のようになります。
type priority = ERR | NOTICE | DEBUG type message = string * priority let (+>) f g = g f let tee f x = f x; x (* ロガーの元となる関数. これを特殊化していく. *) let logger writeMessage mask ((msg, priority) as v) = tee (fun _ -> if priority <= mask then (* 実はバリアントは大小比較できる. 先に定義された方が小さい. *) writeMessage msg) v let stdoutLogger mask ((msg, _) as v) = logger (fun msg -> print_string ("writing to stdout: " ^ msg ^ "\n")) mask v let emailLogger mask ((msg, _) as v) = logger (fun msg -> print_string ("sending via email: " ^ msg ^ "\n")) mask v let stderrLogger mask ((msg, _) as v) = logger (fun msg -> print_string ("sending to stderr: " ^ msg ^ "\n")) mask v let _ = let log x = (* 関数の鎖を作る. *) stdoutLogger DEBUG x +> emailLogger NOTICE +> stderrLogger ERR +> (fun _ -> ()) (* 最後の戻り値をunitにするための壁. *) in log ("Entering funciton y", DEBUG); log ("Step1 complete", NOTICE); log ("An error has occurred", ERR) (* 実行結果 writing to stdout: Entering funciton y writing to stdout: Step1 complete sending via email: Step1 complete writing to stdout: An error has occurred sending via email: An error has occurred sending to stderr: An error has occurred *)
OCamlのコードの興味深い点は、鎖を作る事は(+>)演算子によって構成されており、logger関数とは分離されている点です。Javaの例ではsetNextがLoggerクラスと強く結びついていました。関数の糊付けは関数プログラミングの得意とするところです。
次回に続く!