I'm trying to write a code in F# that allows logging to custom source and using generating random number sing swappable implementations.
Instead of passing logging function/random generator to every function in the application, I'm trying to pass these functions as a context of computation expression.
I've noticed it is very similar requirement to state monad implementations, so I've tried to write something similar.
I have proof of concept working, and generating numbers is working very good, but I cannot make it to work nicely with printf style parameters for my logging function:
module Test
type Simulator = { random : int * int -> int; logger : string -> unit }
module Simulator =
let create = let random = new System.Random() in
{
random = fun (min, max) -> random.Next(min, max)
logger = fun str -> (printfn "%s" str |> ignore)
}
type Simulation<'T> = Simulation of (Simulator -> 'T)
module Simulation =
/// Runs a simulation given a simulator
let inline run state simulation = let (Simulation(play)) = simulation in play state
/// Returns a random number
let random min max = Simulation (fun simulator -> simulator.random (min, max))
/// Writes to simulation log
let log = Simulation (fun simulation -> Printf.ksprintf simulation.logger)
type SimulationBuilder() =
member this.Bind (x, f) = let (Simulation(simulation)) = x in Simulation (fun simulator -> f (simulation simulator))
member this.Return (x) = x
let simulate = new SimulationBuilder()
let simpleSimulation =
simulate
{
//very nice, working
let! x = Simulation.random 2 12
//this is working, but verbose
let! logger = Simulation.log
do logger "Value: %d" x
//I want to write
//do! Simulation.log "Value: %d" x
// or something similar
return x;
}
Simulation.run Simulator.create simpleSimulation |> ignore
Can someone help me? I'm pretty new to writing custom computation expressions.
EDIT
Note that log function could have signature
let log str = Simulation (fun simulation -> simulation.logger str)
and it would be easy to call:
simulate
{
...
do! Simulation.log "Hello world!"
...
}
but here I am losing ability to pass format parameters without using sprintf
EDIT
There is an error in bind implementation, should be:
member this.Bind (x, f) = let (Simulation(simulation)) = x in Simulation (fun simulator -> Simulation.run simulator (f (simulation simulator)))