简体   繁体   中英

Random / State workflow in F#

I'm trying to wrap my head around mon-, err, workflows in F# and while I think that I have a pretty solid understanding of the basic "Maybe" workflow, trying to implement a state workflow to generate random numbers has really got me stumped.

My non-completed attempt can be seen here:

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"

Edit: Actually got it to work! Not exactly sure how it works though, so if anyone wants to lay it out in a good answer, it's still up for grabs. Here's my working code:

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

There are two things that make it harder to see what your workflow is doing:

  1. You're using a function type for the type of your monad,
  2. Your workflow not only builds up the computation, it also runs it.

I think it's clearer to follow once you see how it would look without those two impediments. Here's the workflow defined using a DU wrapper type:

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()

And here is how you use it:

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"

In this version you separately build up the computation (and store it inside the Random type), and then you run it passing in the initial state. Look at how types on the builder methods are inferred and compare them to what MSDN documentation describes.

Edit : Constructing a builder object once and using the binding as an alias of sorts is mostly convention, but it's well justified in that it makes sense for the builders to be stateless. I can see why having parameterized builders seems like a useful feature, but I can't honestly imagine a convincing use case for it.

The key selling point of monads is the separation of definition and execution of a computation.

In your case - what you want to be able to do is to take a representation of your computation and be able to run it with some state - perhaps 0, perhaps 42. You don't need to know the initial state to define a computation that will use it. By passing in the state to the builder, you end up blurring the line between definition and execution, and this simply makes the workflow less useful.

Compare that with async workflow - when you write an async block, you don't make the code run asynchronously. You only create an Async<'a> object representing a computation that will produce an object of 'a when you run it - but how you do it, is up to you. The builder doesn't need to know.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM