[sending slightly improved version to blog]
open System
open System.Collections.Generic
type Stats = Map<string, obj>
// Lens code based on http://www.fssnip.net/7Pk/title/Polymorphic-lenses by Vesa Karvonen
type Lens<'s,'t,'a,'b> = ('a -> Option<'b>) -> 's -> Option<'t>
module Lens =
let view l s =
let r = ref Unchecked.defaultof<_>
s |> l (fun a -> r := a; None) |> ignore
!r
let over l f =
l (f >> Some) >> function Some t -> t | _ -> failwith "Impossible"
let set l b = over l <| fun _ -> b
let lens get set = fun f s ->
(get s |> f : Option<_>) |> Option.map (fun f -> set f s)
module Props =
let prop<'t> (propName: string) f =
Lens.lens
(fun x -> match Map.tryFind propName x with | Some(:? 't as v:obj) -> (v |> unbox<'t>) | _ -> Unchecked.defaultof<'t>)
(fun v x -> Map.add propName v x)
f
let set prop v stats = stats |> Lens.set prop (box v)
let get prop stats = stats |> Lens.view prop
open Props
let name = prop<string> "Name"
let id = prop<string> "ID"
let iq = prop<int> "IQ"
let me = Map.empty |> set name "Max" |> set id "30777" |> set iq 4
printfn "%s (%s) is %s" (get name me) (get id me) (if get iq me > 100 then "smart" else "not smart")
// prints "Max (30777) is not smart"
// Did you catch that? iq returns an int, not a boxed int, and name statically knows that it's returning a string! Magic!