丸めワークフローはモナドなのか?モナドとワークフローの境界を探る

 Haskellでいうところのモナドのお約束は
  1. 型構築子
  2. Return
  3. Bind
という三つ組みで、それぞれに満たすべき性質があります。

このうち、下の二つはF#のワークフローでも必須のものなのですが、最初の型構築子については必ずしも要求されていないところがあります。
それがよく分かるのが、「Programming F#」の「丸めワークフロー」です。
type ReoundingWorkflow(sigDigs : int) =
  let round (x : float) = Math.Round(float x, sigDigs)
 
  member this.Bind(result : float, rest : float -> float) =
    let result' = round result
    rest result'
  
  member this.Return (x : float) = round x

let withPrecision sigDigs = new RoundingWorkflow(sigDigs)

let rest = 
  withPrecision 3 {
    let! x = 2.0 / 12.0
    let! y = 3.5
    return x / y
  }

 乗除算の続く計算で、計算ごとに指定の精度に桁数をそろえる機能がこの「丸めワークフロー」です。
 とまぁそんなことは細かいことなのです。

 ここで注目してほしいのは、Bindの最初の引数の型です。通常ここには型構築子に包まれた型、F#の場合は値を判別共用体が来るものですが、このサンプルでは単純なfloat型となっています。しかし、プログラムの形としては完全にBindの構造を持っていますし、ワークフローとしても完全に動作しています。
 このソースはモナドといえるのでしょうか?全てのワークフローはモナドというわけでもないのでしょうか?

Bindの最初の引数が判別共用体である目的

 実は圏論のことはよく分かっていない(というか全く分かっていない)のですが、ワークフローでの判別共用体の使われ方についてはなんとなく見えてきた気がしています。そんな僕の現状の理解で、「丸めワークフロー」がなぜ判別共用体で実装されていないのかを考えてみたいと思います。

 ワークフローの実装で、判別共用体が必要なケースとはどのような時なのでしょうか?例えば、StatefulFunc/StateBuilderのケースでは、実はStatefulFuncという判別共用体などなくとも全く同じ動作をするコードを書くことができます。

type StateBuilder() =
  member this.Bind(
                    result : 'state -> 'a * 'state,
                    restOfComputation : 'a -> 'state -> 'b * 'state
                  ) =
    fun initialState ->
      let result, updatedState = result initialState
      restOfComputation result updatedState

  member this.Return(x : 'a) =
    fun initialState -> x, initialState
  
  
let state = StateBuilder()

let Add x = fun (currentTotal, history) -> (), (currentTotal + x, (sprintf "%dを加算" x) :: history)

let calculatorActions =
  state {
    do! Add 2
    do! Add 3
  }

let res, stat = calculatorActions (0, [])

 実行結果
val stat : int * string list = (5, ["3を加算"; "2を加算"])
val res : unit = ()

 少なくとも、同じことができるか、という観点だけならStatefulFuncなしでも全く同じ動作をするコードは書けるようです。それでもわざわざStatefulFuncでラップする意味は何なのでしょうか?いや、分からないから尋ねてるだけなんですけど・・恐らく、Builderを実装する立場から見て、その上で動く関数に実装制限を設けるためなのではないかと思うのですが・・・

実装上どうしても必要なケースとそうでもないケース

 StateBuilderにでは、判別共用体は必ずしも必要なものではなさそうです。RoundingWorkflow(丸めワークフロー)でもそうでした。
 しかし、最初のサンプルで出ているDefinedBuilder(Maybeモナド)のケースでは、判別共用体を使わないと実装できません。
 この二つの違いは、単に最初の引数から入ってきた値の実際を、matchにかけて処理を分岐する必要があるかないか、ということでしかありません。StateBuilderにしろRoundingWorkflowにしろ、やる処理は一つで分岐がありません。こういうケースでは、判別共用体を使用する実装上の都合は発生しません。しかし、DefinedBuilder(Maybeモナド)では、判別共用体がどちらにmatchしたかで処理を切り替える必要があります。難しい理屈を抜きにするとこういうことなのです。

結論

 結局のところ、丸めワークフローはモナドの要件を満たしていないと言えそうです。しかし、あえてモナドという言葉を選ばず「ワークフロー」という言葉を選んだF#の設計チームは、もう少しゆるいルールでモナド的なものを考えてもいいと思っていたのではないかと思うのです。ワークフローの説明に「圏論」という言葉が使われるケースはごくわずかですし、結合法則も要件に登場しません。Haskellのモナドの「形」だけを持ち込んで、モナドっぽいことも含めてモナドを実装できるようにしたものがF#のワークフローなのではないかと思うのです。「モナドっぽいの好き」って、世代か。

(文責:片山 功士  2012/01/01)


今日: -
昨日: -
トータル: -
最終更新:2012年02月03日 01:59