Reputation: 15391
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
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
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
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