gpt4 book ai didi

f# - 状态单子(monad)如何绑定(bind)到外部上下文中

转载 作者:行者123 更新时间:2023-12-01 00:35:21 25 4
gpt4 key购买 nike

我试图理解状态单子(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 的额外调用。 .
它们是如何产生的?
我从表面上理解绑定(bind)到那些外部上下文一定是 state monad 的魔力,但这种机制是如何工作的?
然后还有一个 let! pos2 = ...在函数 turn这也会触发 Bind但这次只有一次而不是以前的3次!

期待你的解释

最佳答案

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

您在工作流程中建立的计算是 'st -> State<'st, 'a> 类型的一大功能。 .您调用的地方run事实上,您将这个函数应用于初始参数 - 这就是通过绑定(bind)传递的内容,进而从“父级”move 传递。工作流程到 turn .因此,并不是您的嵌套工作流程正在访问外部的任何内容——而是您自己将其传递到那里。

您做出的一个非标准选择(可能不会更容易理解正在发生的事情)是您的 State monad 不是纯 state monad,而是结合了 State 和 Either/Maybe monad 的各个方面(通过状态类型的错误案例)。当您定义 State 类型时,您的实际一元类型是我之前提到的函数类型。

一种典型的方法是将类型定义为如下所示:

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

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

至于问题的第二部分,您的路径上确实有三个绑定(bind) - do! move() , let! x = turn()let! pos1 = state.Get() - 这就是你在日志中看到的。我认为事情发生的顺序在这里可能很棘手。

记住绑定(bind)是如何脱糖的:
{| let! pattern = expr in cexpr |} => builder.Bind(expr, (fun pattern -> {| cexpr |}))

这意味着首先你评估 expr , 只有这样 Bind被调用,最后剩下的计算 cexpr .在你的情况下,你去“三个深度绑定(bind)”来评估第一个 expr - 这是对 Get() 的调用- 然后你开始解决你的绑定(bind)堆栈,在某个时候调用另一个绑定(bind)作为 cexpr 的一部分.

如果在计算 let result = p state 之后添加另一个打印语句,可能会更容易了解实际情况。在 Bind ,这是解除绑定(bind)的时间。

关于f# - 状态单子(monad)如何绑定(bind)到外部上下文中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41694318/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com