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.
// 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
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.