我正试图围绕F#中的mon-err工作流程,而我认为我对基本的“可能”工作流程非常了解,尝试实现状态工作流来生成随机数真让我难过。
我的未完成尝试可以在这里看到:
let randomInt state =
let random = System.Random(state)
// Generate random number and a new state as well
random.Next(0,1000), random.Next()
type RandomWF (initState) =
member this.Bind(rnd,rest) =
let value, newState = rnd initState
// How to feed "newState" into "rest"??
value |> rest
member this.Return a = a // Should I maybe feed "initState" into the computation here?
RandomWF(0) {
let! a = randomInt
let! b = randomInt
let! c = randomInt
return [a; b; c]
} |> printfn "%A"
编辑:实际上让它发挥作用!不完全确定它是如何工作的,所以如果有人想要在一个好的答案中列出它,它仍然可以争取。这是我的工作代码:
type RandomWF (initState) =
member this.Bind(rnd,rest) =
fun state ->
let value, nextState = rnd state
rest value nextState
member this.Return a = fun _ -> a
member this.Run x = x initState
答案 0 :(得分:4)
有两件事情让你很难看到你的工作流程在做什么:
一旦你看到没有这两个障碍的情况,我认为它会更加清晰。这是使用DU包装器类型定义的工作流程:
type Random<'a> =
Comp of (int -> 'a * int)
let run init (Comp f) = f init
type Random<'a> with
member this.Run(state) = fst <| run state this
type RandomBuilder() =
member this.Bind(Comp m, f: 'a -> Random<_>) =
Comp <| fun state ->
let value, nextState = m state
let comp = f value
run nextState comp
member this.Return(a) = Comp (fun s -> a, s)
let random = RandomBuilder()
以下是您使用它的方式:
let randomInt =
Comp <| fun state ->
let rnd = System.Random(state)
rnd.Next(0,1000), rnd.Next()
let rand =
random {
let! a = randomInt
let! b = randomInt
let! c = randomInt
return [a; b; c ]
}
rand.Run(0)
|> printfn "%A"
在这个版本中,你单独构建计算(并将其存储在Random类型中),然后在初始状态下运行它。查看如何推断构建器方法上的类型,并将它们与MSDN documentation描述的内容进行比较。
编辑:构建一次构建器对象并使用绑定作为排序的别名主要是惯例,但它很合理,因为构建器是无状态的。我可以看到为什么参数化构建器似乎是一个有用的功能,但我不能老实地想象一个令人信服的用例。
monads的关键卖点是计算的定义和执行的分离。
在您的情况下 - 您希望能够做的是对您的计算进行表示,并能够以某种状态运行它 - 也许是0,也许是42。您不需要知道最初的state来定义将使用它的计算。通过将状态传递给构建器,您最终会模糊定义和执行之间的界限,这只会使工作流程变得不那么有用。
将其与async
工作流进行比较 - 当您编写异步块时,不要使代码异步运行。您只创建一个Async<'a>
对象,表示在运行时会生成'a
对象的计算 - 但是您如何操作,取决于您。建筑师不需要知道。