【问题标题】:Random / State workflow in F#F# 中的随机/状态工作流
【发布时间】:2015-07-10 16:41:55
【问题描述】:

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

【问题讨论】:

  • 你有什么问题?
  • 如何解决?或者一般应该如何构建这样的 monad/工作流。
  • 这里很好地描述了 state monad 是如何工作的。它有点冗长(3 部分),但值得一读。也许它会帮助fsharpforfunandprofit.com/posts/monadster
  • 啊,我喜欢 fsharpforfunandprofit!实际上,在编写此代码时,我一直在研究这个系列:fsharpforfunandprofit.com/posts/computation-expressions-intro。感谢您的链接!
  • 通过使用生成的数字播种下一个随机数,您可能会陷入循环。 IE。 Random(123).Next(0,1000).Next() == 123。如果能从 Random 中获取种子就好了,但它没有暴露。最好的选择是滚动你自己的 Random 类,或者使用 Random 是可序列化的事实并将其序列化并将该字符串传出和传入。

标签: f# state monads state-monad


【解决方案1】:

有两件事让您更难了解您的工作流程正在做什么:

  1. 您正在使用函数类型作为 monad 的类型,
  2. 您的工作流不仅建立计算,还运行它。

我认为,一旦您看到没有这两个障碍的情况后,它会更清晰。下面是使用 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 描述的内容进行比较。

编辑:构造一个构建器对象并使用绑定作为各种别名大多是惯例,但它的合理性在于构建器无状态是有意义的。我可以理解为什么具有参数化构建器似乎是一个有用的功能,但老实说,我无法想象一个令人信服的用例。

monad 的主要卖点是计算的定义和执行的分离。

在您的情况下 - 您想要做的是获取计算的表示并能够以某种状态运行它 - 也许是 0,也许是 42。您不需要知道初始状态定义将使用它的计算。通过将状态传递给构建器,您最终会模糊定义和执行之间的界限,这只会使工作流变得不那么有用。

将其与async 工作流进行比较 - 当您编写异步块时,您不会使代码异步运行。您只需创建一个 Async&lt;'a&gt; 对象,该对象代表一个计算,当您运行它时将生成一个 'a 对象 - 但如何操作取决于您。建造者不需要知道。

【讨论】:

  • 回复晚了,但非常感谢!我终于开始理解这些东西了,写出绑定调用嵌套真的很有帮助。围绕工作流程获得一个漂亮界面的机制也非常有用。但是有一个问题,似乎有一种约定不直接使用构建器类型,而是制作一个对象并使用它。有什么具体原因吗?我认为直接使用该类型为您提供了一个有用的机会来提供例如初始状态(就像我所做的那样)。
  • @monoceres:我在编辑中给了你回复(因为它变得很长)。
猜你喜欢
  • 2012-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-26
  • 2012-04-02
  • 1970-01-01
相关资源
最近更新 更多