我正在努力了解州立大学,我必须承认,我很困惑。 我创建了一个计算表达式,并添加了许多打印语句,因此我可以跟进谁在什么时候被调用。
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倍!
期待您的解释
答案 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语句,那么可能会更容易看到真正发生的事情,这是绑定被解除的时候。