Search code examples
f#singletonmemoizationdiscriminated-union

How to memoize the result of a property of a Discriminated Union in F#


I have a Discriminated Union ("DU") type and a property OR method which computes something based on the DU instance. I am trying to achieve a pattern where the instance property performs the calculation the first time it is requested and then remembers the result - akin to a Singleton Pattern in Object Oriented Terms.

I am finding this challenging without the aid of a local instance variable to store the state of things...

I have tried simple memoization of a method but I then run into the problem of not having anywhere (in the instance) to store the memoized result.

Note: there will be many instances of this DU type in my application.

Code

// Terrible mutable variable needed alongside the DU to achieve a singleton-like pattern
let mutable result: int option = None

type DU = 
    | Good of int
    | Bad of int

with
    // behaves like a Singleton
    member this.GetSomething = 
        match result with
        | None ->        
            printfn "Big bad side-effect to let us know it's a first time"
            // big bad calculation that we only want to do once
            let x = 1 + 1
            // "memoize" it
            result <- Some x
            x
        | Some y -> y


let instance = Good 1
let f1 = instance.GetSomething  // first time
let f2 = instance.GetSomething  // second call - no side effect1

Solution

  • You can't memoize inside of an immutable value, because memoization involves changing and maintaining state.

    Obviously, you can do it outside of an immutable value:

    let smth = getSomething "foo"
    

    As long as you reuse smth instead of calling getSomething "foo" again, you've essentially memoized the result. This is safe if getSomething is referentially transparent; otherwise, it's not.

    From the sample code posted in the OP, it looks more like you're looking for lazy initialization, which you can get from the Lazy<T> class. You'll still need an object in which to store the Lazy<T> instance, though.

    open System
    
    type MyObject() =
        let result =
            lazy
            printfn "Big bad side-effect to let us know it's a first time"
            // big bad calculation that we only want to do once
            1 + 1
        member this.GetSomething = result.Value
    

    As you can see, in F# you can also use lazy expressions for this.

    > let mo = MyObject ();;    
    val mo : MyObject
    
    > let smth1 = mo.GetSomething;;
    Big bad side-effect to let us know it's a first time    
    val smth1 : int = 2
    
    > let smth2 = mo.GetSomething;;    
    val smth2 : int = 2
    

    The MyObject class may look immutable, but that's only because state is being kept inside of the lazy expression.