Don Syme blogged this quite some years ago but it just came up in a design review on my team this afternoon and it bears repeating.
let memoize f = let cache = new Dictionary<_,_>() (fun x -> match cache.TryGetValue x with | true, res -> res | _ -> let res = f x in cache.Add(x, res); res)
This generic function takes a function and returns a new caching version of it. So simple!
You can then take some expensive function such as the standard example Fibonacci:
let rec fib = function | 1 | 2 -> 1 | n -> fib (n - 1) + fib (n - 2)
Which is quite expensive when written this naïvely because of the exponential number of recursive calls; most with the same arguments.
> fib 42;; Real: 00:00:01.663, CPU: 00:00:01.669, GC gen0: 0, gen1: 0, gen2: 0 val it : int = 267914296
It takes 1.663 seconds for fib 42!
Okay, let’s make a memoized version:
let rec fastFib = memoize (function | 1 | 2 -> 1 | n -> fastFib (n - 1) + fastFib (n - 2))
It really can’t be any easier than that! Note that because it's a recursive function simply saying let fastFib = memoize fib doesn't work.
> fastFib 42;; Real: 00:00:00.000, CPU: 00:00:00.001, GC gen0: 0, gen1: 0, gen2: 0 val it : int = 267914296