A Sample of the Memoization Pattern in F#
Pieter Breed has been asking some useful questions on the F# list recently about using the "Map" type. I really appreciate it when people take the time to let us know when data structures are tricky to learn to use in F#.
Looking at Pieter's blog made me think it might be instructive to show how the memoization pattern looks in F# (or at least one example of the pattern). I've included the code below:
#light
open System
open System.Text
open System.Collections.Generic
// Save computed results by using an internal dictionary.
// Note that memoize is inferred to have type
// memoize: ('a -> 'b) -> ('a -> 'b)
let memoize f =
let cache = Dictionary<_, _>()
fun x ->
if cache.ContainsKey(x) then cache.[x]
else let res = f x
cache.[x] <- res
res
// An example, taken from Pieter's blog
let memoizedAppend =
memoize (fun input ->
printfn "Working out the value for '%A'" input
String.concat ", " [ for i in 0 .. 9 -> sprintf "%d: %s" i input ])
And using this in F# Interactive:
> Console.WriteLine(memoizedAppend("this"))
Working out the value for '"this"'
0: this, 1: this, 2: this, 3: this, 4: this, 5: this, 6: this, 7: this, 8: this, 9: this
> Console.WriteLine(memoizedAppend("this"))
0: this, 1: this, 2: this, 3: this, 4: this, 5: this, 6: this, 7: this, 8: this, 9: this
Some things to note here:
- Working out the value for 'this' is only printed once, as we expcet
- The generic type of memoize is computed automatically. This makes the code short and clear (once you get used to the F# syntax). This is known as automatic generalization, and is a key part of type inference and succinct coding in all typed functional languages.
- The example uses double lookup - this can be changed if necessary, but I thought it instructive to keep the same structure as Pieter's sample
There will be a section on memoization and caching in the Expert F# book, in the "Techniques" chapter
Update: Here's the single-lookup version of this:
let memoize f =
let cache = Dictionary<_, _>()
fun x ->
let mutable ok = Unchecked.defaultof<_>
let res = cache.TryGetValue(x,&ok)
if ok then res
else let res = f x
cache.[x] <- res
res
.Update: here's a version that uses a single mutable reference cell holding an immutable tree-based Map value.
let memoize f =
let cache = ref Map.empty
fun x ->
match (!cache).TryFind(x) with
| Some res -> res
| None ->
let res = f x
cache := (!cache).Add(x,res)
res
don
Comments
Anonymous
May 31, 2007
I find myself not liking the "pattern" of doing a .ContainsKey then a seperate call to access the value. Much better is this pattern (sorry C# here, don't know F# yet): if (!cache.TryGetValue(x, out res) { res = f(x); cache[x] = res; } return res;Anonymous
June 03, 2007
I have a very basic question - if you are giving an example about Map, why didn't you use type Map from the FSharp namespace instead of using the .Net Dictionary. Does it make a difference?Anonymous
June 03, 2007
The comment has been removedAnonymous
June 04, 2007
This is slightly OT, but it is amazing what you learn from watching other people code. It seems like for some problems I see the solution before I make the problem. For others, I make the mistake repeatedly and never realise what's going on, until I see what somebody else is doing. In this case its the double-lookup. I just never realised that I do the lookup twice. Thanks Don.Anonymous
July 11, 2007
I have to say F# is looking more impressive everyday, F# is so much more pleasent with the lightweight syntax. I have to say guys well done for the great progress, serious F# needs to replace C# as the flagship language :PAnonymous
July 16, 2007
The comment has been removedAnonymous
October 22, 2008
Memoize that use Dictionary is way faster then version with Map. In my test example, memoize with Map is even slower then no memoize :)Anonymous
February 20, 2009
This week we have practical examples of Lazy Evaluation with Memoization, an interview with Don SymeAnonymous
June 21, 2012
I think memoization pattern can be greatly enhanced, from a practical point of view, by using a generic version of it. I will post a version of it. That is an excellent use case for type classes actually.Anonymous
December 11, 2013
The names "res" and "ok" are not in the right place in the first update. correct is: let memoize f = let cache = Collections.Generic.Dictionary<_ , >() fun x -> let mutable res = Unchecked.defaultof<> let ok = cache.TryGetValue(x,&res) if ok then res else let res = f x cache.[x] <- res res