2017-01-17 27 views
4

私は状態モナドを理解しようとしています。私は認めなければならないと大変混乱しています。 私は計算式を作成し、多くの印刷ステートメントを追加したので、いつ誰が呼び出されるかをフォローアップすることができます。状態モナドは外部コンテキストにどのようにバインドするのですか

type State<'st,'a> = 
    | Ok of 'a * 'st 
    | Error of string 
and StateMonadBuilder() = 
    member b.Return(x) = printfn "100 Return %A" x; fun s -> Ok (x, s) 
    member b.ReturnFrom(x) = printfn "100 ReturnFrom %A" x; x 
    member b.Bind(p, rest) = 
     printfn "100 Bind:: %A %A" p rest 
     fun state -> 
      printfn "200 Bind:: %A %A" p rest 
      let result = p state in 
      match result with 
      | Ok (value,state2) -> (rest value) state2 
      | Error msg -> Error msg 

    member b.Get() = 
     printfn "100 Get" 
     fun state -> 
      printfn "200 Get :: %A" state 
      Ok (state, state) 
    member b.Put s = fun state -> Ok ((), s) 

let state = StateMonadBuilder() 

let turn() = 
    state { 
     printfn "100 turn::" 
     let! pos1 = state.Get() 
     printfn "200 turn:: %A" pos1 
     let! pos2 = state.Get() 
     printfn "300 turn:: %A" pos1 
     return! state.Put(fst pos1, snd pos1 - 1) 
    } 

let move() = 
    state { 
     printfn "100 move::" 
     let! x = turn() 
     printfn "200 move:: %A" x 
     let! y = turn() 
     printfn "200 move:: %A" y 
     return x 
    } 

let run() = 
    state { 
     printfn "100 run::" 
     do! move() 
    } 

run() (5,5) |> ignore 

上記のコードは、私は、この出力の最初の5行を理解する次の出力

100 run:: 
100 move:: 
100 turn:: 
100 Get 
100 Bind:: <fun:[email protected]> <fun:[email protected]> 
100 Bind:: <fun:[email protected]> <fun:[email protected]> 
100 Bind:: <fun:[email protected]> <fun:[email protected]> 
200 Bind:: <fun:[email protected]> <fun:[email protected]> 
200 Bind:: <fun:[email protected]> <fun:[email protected]> 
200 Bind:: <fun:[email protected]> <fun:[email protected]> 
200 Get :: (5, 5) 
200 turn:: (5, 5) 
100 Get 
100 Bind:: <fun:[email protected]> <fun:[email protected]> 
200 Bind:: <fun:[email protected]> <fun:[email protected]> 
200 Get :: (5, 5) 
300 turn:: (5, 5) 
100 ReturnFrom <fun:[email protected]> 
200 move:: <null> 
100 Return <null> 
100 Return <null> 

を印刷します。明らかにrunはと、turnGetとなります。 そしてBindへの呼び出しをトリガーするlet! pos1 = ...があります。ここまでは順調ですね。しかし、Bindへの追加の呼び出しがあります。 彼らはどのようにして生まれますか? 私は表面的なレベルで、これらの外部コンテキストへのバインディングは何とか国家モナドの魔法でなければならないと理解しますが、このメカニズムはどのように機能しますか? そして、turnのにもう1つのlet! pos2 = ...がありますが、これは前回と同じように3回だけではありません。

関与魔法、すべての煙と鏡はありませんあなたのexplainations

+0

生成されたdesugaredコードを反射板またはILSpyで見てみてください。 – scrwtp

+0

実際には、その技術的側面についてはそれほど疑問ではありません - さらに、言語レベルでこれらの外部スコープにアクセスすることも可能です – robkuz

+0

'<@ state {...} @>あなたが不明確な場合に評価します。どのような追加情報が役立つかは私には分かりません。 – kvb

答えて

4

を楽しみにしています。

ワークフローで構築する計算は、タイプ'st -> State<'st, 'a>の大きな関数の1つです。 runと呼ぶ場所は、実際には最初の引数にこの関数を適用します。つまり、バインドを通過してから、「親」moveワークフローからturnに渡されます。だからあなたのネストされたワークフローが外部のものにアクセスしているわけではありません。

あなたが作った非標準的な選択肢は、おそらくそれが起こっていることを理解しやすくしていません。あなたの状態モナドは純粋な状態のモナドではなく、むしろStateとEither/Maybeモナド(状態タイプのエラーの場合)。 State型を定義している間、実際のモナド型はここで前述した関数型です。

典型的なアプローチは、このようなものとして型を定義するために、次のようになります。

type State<'st, 'a> = State of ('st ->'a * 'st), 

つまり、あなたが関数の型のラッパーとして単一ケース組合を使用するか、または単にラッピングタイプせずに関数を使用します。エラー処理は通常、状態モナドが扱う問題ではありません。

質問の2番目の部分については、あなたのパスには、do! move()let! x = turn()let! pos1 = state.Get()の3つのバインドがあります。これがログに表示されます。物事が起こる順序はここでは難しいかもしれないと私は思う。

バインドが脱糖する方法を覚えておいてください:

{| let! pattern = expr in cexpr |} => builder.Bind(expr, (fun pattern -> {| cexpr |})) 

これが何を意味するのかは、最初にあなたがexprを評価し、だけにしてBindが呼び出され、そして計算cexprの最後に残ります。あなたのケースでは、最初にexprGet()への呼び出し)を評価するために "3バインドディープ"に行きます。その後、バインドのスタックを解決し始めます。ある時点で別のバインドをcexprの一部として呼び出します。

let result = p stateBindに計算した後に別の印刷ステートメントを追加すると、実際に何が起こっているのか分かりやすくなるでしょう。これは、バインドが解除されているときです。

+0

"3つのバインド"の事についてもう少し詳細を加えました。 – scrwtp

+0

thx。私の最大の精神的結び目は、私は2つのパスがあるという事実を見ていませんでした。コンパイル時パスとランタイム・パス – robkuz

+0

はい - 素敵な中括弧構文は単なるコード成果物です。コンパイル時には、互いにネストされた一連のビルダー・メソッド呼び出しに変換されます。コンパイル時にはコードの実行は行われません。これは純粋に機械的な変換です。そのため、すべてのビルダーは、変換プロセスに適合するように、メソッド名と型の同じパターンに従っています。 – scrwtp

関連する問題