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

F# F中的随机/状态工作流#,f#,state,monads,state-monad,F#,State,Monads,State Monad,我正试图用F#来概括mon-,呃,工作流,虽然我认为我对基本的“可能”工作流有着非常扎实的理解,但试图实现一个状态工作流来生成随机数确实让我感到困惑 我未完成的尝试可以在这里看到: let randomInt state = let random = System.Random(state) // Generate random number and a new state as well random.Next(0,1000), random.Next() type

我正试图用F#来概括mon-,呃,工作流,虽然我认为我对基本的“可能”工作流有着非常扎实的理解,但试图实现一个状态工作流来生成随机数确实让我感到困惑

我未完成的尝试可以在这里看到:

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的类型
  • 您的工作流不仅构建了计算,还运行了计算
  • 我认为,一旦你看到没有这两个障碍的情况下它会是什么样子,你就可以更清楚地理解它。以下是使用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()
    
    type Random(随机)=
    公司
    let值,nextState=m状态
    设comp=f值
    运行下一个州公司
    成员返回(a)=Comp(乐趣s->a,s)
    让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"
    
    let randomInt=
    公司
    设rnd=System.Random(状态)
    rnd.Next(01000),rnd.Next()
    让兰德=
    随机的{
    设!a=randomInt
    设!b=randomInt
    设!c=randomInt
    返回[a;b;c]
    }
    rand.Run(0)
    |>printfn“%A”
    
    在这个版本中,分别构建计算(并将其存储在Random类型中),然后以初始状态运行它。查看生成器方法上的类型是如何推断的,并将它们与描述的类型进行比较

    编辑:一次构造一个构建器对象并使用绑定作为排序别名基本上是惯例,但构建器是无状态的是合理的。我可以理解为什么参数化构建器看起来是一个有用的特性,但我不能诚实地想象它有一个令人信服的用例

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

    在您的例子中,您希望能够做的是获取您的计算的表示,并能够使用某种状态运行它—可能是0,可能是42。您不需要知道初始状态就可以定义将使用它的计算。通过将状态传递给构建器,最终会模糊定义和执行之间的界限,这只会降低工作流的实用性


    async
    workflow相比,当您编写一个异步块时,您不会使代码异步运行。您只创建了一个
    Async,有两件事使您更难看到您的工作流在做什么:

  • 您正在使用函数类型作为monad的类型
  • 您的工作流不仅构建了计算,还运行了计算
  • 我认为,一旦你看到没有这两个障碍的情况下它会是什么样子,你就可以更清楚地理解它。以下是使用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()
    
    type Random(随机)=
    公司
    let值,nextState=m状态
    设comp=f值
    运行下一个州公司
    成员返回(a)=Comp(乐趣s->a,s)
    让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"
    
    let randomInt=
    公司
    设rnd=System.Random(状态)
    rnd.Next(01000),rnd.Next()
    让兰德=
    随机的{
    设!a=randomInt
    设!b=randomInt
    设!c=randomInt
    返回[a;b;c]
    }
    rand.Run(0)
    |>printfn“%A”
    
    在这个版本中,分别构建计算(并将其存储在Random类型中),然后以初始状态运行它。查看生成器方法上的类型是如何推断的,并将它们与描述的类型进行比较

    编辑:一次构造一个构建器对象并使用绑定作为排序别名基本上是惯例,但构建器是无状态的是合理的。我可以理解为什么参数化构建器看起来是一个有用的特性,但我不能诚实地想象它有一个令人信服的用例

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

    在您的例子中,您希望能够做的是获取您的计算的表示,并能够使用某种状态运行它—可能是0,可能是42。您不需要知道初始状态就可以定义将使用它的计算。通过将状态传递给构建器,最终会模糊定义和执行之间的界限,这只会降低工作流的实用性


    async
    workflow相比,当您编写一个异步块时,您不会使代码异步运行。您只创建了一个
    async,您的问题是什么?如何修复它?或者一般来说如何构造这样的monad/工作流。下面是state monad如何工作的一个很好的描述。它有点长(3部分),但值得一读。也许这会有帮助啊,我喜欢FSharp的乐趣和利润!实际上,在编写这段代码时,我一直在研究以下系列:。谢谢你的链接!通过使用生成的数字播种下一个随机数,您可能最终进入循环。即随机(123)。下一个(01000)。下一个()==123。这将是很好的,从随机种子,但它没有暴露。最好的替代方法是滚动您自己的Random类,或者使用Random可序列化的事实,将其序列化,并将该字符串传入传出。您的问题是什么?如何修复它?或者一般来说如何构造这样的monad/工作流。下面是state monad如何工作的一个很好的描述。它有点长(3部分),但值得一读。也许这会有帮助啊,我喜欢FSharp的乐趣和利润!实际上,在编写这段代码时,我一直在研究以下系列:。谢谢你的链接!通过使用生成的数字播种下一个随机数,您可能最终进入循环。