状态monad如何绑定到外部环境中

时间:2017-01-17 10:11:06

标签: f# state-monad

我正在努力了解州立大学,我必须承认,我很困惑。 我创建了一个计算表达式,并添加了许多打印语句,因此我可以跟进谁在什么时候被调用。

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

以上代码打印以下输出

100 run::
100 move::
100 turn::
100 Get
100 Bind:: <fun:Get@301> <fun:turn@312>
100 Bind:: <fun:Bind@292-2> <fun:move@322>
100 Bind:: <fun:Bind@292-2> <fun:run@329>
200 Bind:: <fun:Bind@292-2> <fun:run@329>
200 Bind:: <fun:Bind@292-2> <fun:move@322>
200 Bind:: <fun:Get@301> <fun:turn@312>
200 Get :: (5, 5)
200 turn:: (5, 5)
100 Get
100 Bind:: <fun:Get@301> <fun:turn@314-1>
200 Bind:: <fun:Get@301> <fun:turn@314-1>
200 Get :: (5, 5)
300 turn:: (5, 5)
100 ReturnFrom <fun:Put@304>
200 move:: <null>
100 Return <null>
100 Return <null>

我理解这个输出的前5行。显然run调用move来电turn来电Get。 然后有let! pos1 = ...触发对Bind的调用。到现在为止还挺好。但随后还有Bind的额外电话。 它们是如何形成的? 我在一个肤浅的层面上理解,绑定到那些外部环境必须以某种方式成为状态monad的魔力,但这种机制如何运作? 然后在函数let! pos2 = ...中还有另一个turn,它也触发Bind,但这次只有一次不是以前的3倍!

期待您的解释

1 个答案:

答案 0 :(得分:4)

没有任何魔法,所有烟雾和镜子。

您在工作流程中构建的计算是'st -> State<'st, 'a>类型的一个重要功能。您调用run的地方实际上是将此函数应用于初始参数的位置 - 这是通过绑定传递的内容,然后是从“父”move工作流到{{1} }。所以并不是你的嵌套工作流程正在访问外面的任何东西 - 你自己将它传递给那里。

你做出的一个非标准选择 - 可能不会让你更容易理解发生了什么 - 是你的状态monad不是一个纯粹的状态monad,而是它结合了State和Either / Maybe monads的各个方面(通过状态类型的错误情况)。在定义State类型时,这里的实际monadic类型是我之前提到的函数类型。

一种典型的方法是将类型定义为:

turn

即。您使用单个案例联合作为函数类型的包装器,或者只使用不带包装类型的函数。错误处理通常不是monad处理的问题。

至于问题的第二部分,您的路径上有三个绑定 - type State<'st, 'a> = State of ('st ->'a * 'st), do! move()let! x = turn() - 这就是您在日志中看到的内容。我认为事情发生的顺序在这里可能很棘手。

记住绑定是如何去除的:

let! pos1 = state.Get()

这意味着首先评估{| let! pattern = expr in cexpr |} => builder.Bind(expr, (fun pattern -> {| cexpr |})) ,然后才调用expr,最后计算其余的Bind。在你的情况下,你去“三个深度”来评估第一个cexpr - 这是对expr的调用 - 然后你开始解析你的一堆绑定,在某些时候调用另一个绑定作为一部分Get()

如果在cexpr中计算let result = p state之后添加了另一个print语句,那么可能会更容易看到真正发生的事情,这是绑定被解除的时候。