osiire’s blog

ふしぎなそふとやさん

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クラスと強く結びついていました。関数の糊付けは関数プログラミングの得意とするところです。

次回に続く!