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