F#中的随机/状态工作流程

时间:2015-07-10 16:41:56

标签: f# state monads state-monad

我正试图围绕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

1 个答案:

答案 0 :(得分:4)

有两件事情让你很难看到你的工作流程在做什么:

  1. 您正在使用monad类型的函数类型
  2. 您的工作流程不仅可以构建计算,还可以运行它。
  3. 一旦你看到没有这两个障碍的情况,我认为它会更加清晰。这是使用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对象的计算 - 但是您如何操作,取决于您。建筑师不需要知道。