Mathias
Mathias

Reputation: 15391

Sequence constructed from the previous element of the Sequence and another Sequence

For learning purposes I am trying out running a simulation as a sequence with F#. Starting from a sequence of random numbers, map is a straightforward way to generate a sequence of states if the states do not depend on the previous states. Where I run into a problem is when I try to do something like:
State(i+1) = F (State(i), random number)
I managed to get something working by using unfold, passing in the random generator along the lines of

  let unfold (day:State,rnd:Random) =
    let rand = rnd.NextDouble()
    let nextDay = NextState day rand
    Some (nextDay, (nextDay, rnd))

However, at least to my inexperienced eyes, something about passing around the Random instance seems fishy. Is there a way to achieve something similar but passing in a sequence of random numbers, rather than the generator?

Upvotes: 2

Views: 703

Answers (3)

Stephen Swensen
Stephen Swensen

Reputation: 22297

I think your hunch about passing around a Random instance as being fishy is fair: when mutable state is useful it's a good idea to isolate it, so that you benifit from purity as much as possible.

We can isolate the state here by creating a sequence which yields a different set of random numbers upon each iteration

open System
let rndSeq = 
    seq {
        //note that by putting rnd inside seq expression here, we ensure that each iteration of the sequence
        //yields a different sequnce of random numbers
        let rnd = new Random()
        while true do yield rnd.NextDouble()
    }

then, you can use Seq.scan to iterate the random sequence by mapping elements using a function which is informed by the previous element which was mapped.

let runSimulation inputSeq initialState =
    inputSeq
    |> Seq.scan 
        (fun (previousState:State) (inputElement:float) -> NextState previousState inputElement) 
        initialState

runSimulation rndSeq initialState //run the simulation using a random sequence of doubles greater than or equal to 0.0 and less than 1

You can see as an added bonus here that your simulated input and simulation implementation are no longer bound together, you can run your simulation using any input sequence.

Upvotes: 4

Tomas Petricek
Tomas Petricek

Reputation: 243061

I'd agree with BrokenGlass that using a global Random instance feels allright in this case. This is a reasonably localized use of mutable state, so it shouldn't be confusing.

As an alternative to unfold, you can consider writing the computation explicitly:

let simulationStates = 
    let rnd = new Random()
    let rec generate (day:State) = seq {
        let rand = rnd.NextDouble()
        let nextDay = NextState day rand 
        yield nextDay
        yield! generate nextDay }
    generate InitialState

Note that the rnd value is local variable with a scope limited only to the definition of simulationStates. This is quite nice way to keep mutable state separate from the rest of the program.

The version using unfold is probably more succinct; this one may be easier to read, so it depends on your personal style preferences.

Upvotes: 4

BrokenGlass
BrokenGlass

Reputation: 160902

Might be against the spirit, but I would just use a global Random instance in this case - alternatively you could define a sequence of random numbers like this:

let randomNumbers = 
    seq  {
            let rnd = new Random();
            while true do
                yield rnd.NextDouble();
         }

Upvotes: 1

Related Questions