Sunday, 28 August 2011

Functional IoC Container Example

open System

type StateMonad() =
    member this.ReturnFrom x = x
    member this.Return x s = (x, s)
    member this.Bind (m, f) s = let (x, s1) = m s
                                in (f x) s1
let state = new StateMonad()

type IEnvironment =
    abstract member UserName : string with get

type ILogger =
    abstract member Log : string -> unit

type Container = { Environment : IEnvironment
                   Logger : ILogger }

let getContainer = fun f -> (f(), f)

let run ctx m = m (fun () -> ctx) |> fst

let rec fac n acc  = state { let! container = getContainer
                             container.Logger.Log (sprintf "%d" acc)
                             if (n = 0) then return acc
                             else return! fac (n - 1) (n * acc) }

let compute n = state { let! container = getContainer                       
                        container.Logger.Log (sprintf "Begin fac %d" n)
                        let! result = fac n 1                                      
                        container.Logger.Log (sprintf "End fac %d" n)
                        let userName = container.Environment.UserName
                        container.Logger.Log (sprintf "Computed by %s" userName)
                        return result }
let prodContainer =
    { Environment = { new IEnvironment with                         
                          member this.UserName
                                  with get () =
                                       Environment.UserName }
      Logger = { new ILogger with
                    member this.Log s =
                        Console.WriteLine ("{0} : {1}", (DateTime.Now), s) } }

let testContainer =
    { Environment = { new IEnvironment with                         
                          member this.UserName
                                  with get () =
                                       "John Doe" }
      Logger = { new ILogger with
                    member this.Log s = Console.WriteLine (" {0} ",  s) } }

printfn "Result %A" (run prodContainer (compute 10))

printfn "Result %A" (run testContainer (compute 10))