如何在 F# 中记忆一个 属性 的可区分联合的结果

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

我有一个区分联合 ("DU") 类型和一个 属性 OR 方法,它根据 DU 实例计算某些东西。我正在尝试实现一种模式,其中实例 属性 在第一次被请求时执行计算,然后记住结果 - 类似于面向对象术语中的单例模式。

如果没有本地实例变量的帮助来存储事物的状态,我发现这具有挑战性...

我已经尝试了一种方法的简单记忆,但是我 运行 遇到了没有任何地方(在实例中)存储记忆结果的问题。

注意:我的申请中会有很多这种DU类型的实例。

代码

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

您不能在不可变值的内部记忆,因为记忆涉及更改和维护状态。

显然,您可以在 不可变值之外执行此操作:

let smth = getSomething "foo"

只要您重复使用 smth 而不是再次调用 getSomething "foo",您实际上已经记住了结果。如果 getSomethingreferentially transparent,这是安全的;否则就不是。

从 OP 中发布的示例代码来看,您看起来更像是在寻找 lazy initialization, which you can get from the Lazy<T> class。不过,您仍然需要一个 对象 来存储 Lazy<T> 实例。

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

如您所见,在 F# 中,您还可以为此使用 lazy 表达式。

> 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

MyObject class 可能看起来是不可变的,但这只是因为状态被保存在 lazy 表达式中。

这样就可以在 DU 中有一个惰性。 因为类型是 Lazy.

type DU = 
    | Good of Lazy<int>  // Lazy not lazy
    | Bad  of Lazy<int>  

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) |> Good
    member this.GetSomething = result

let mo = MyObject()
let f() =
    match mo.GetSomething with
    | Good x -> x.Value
    | Bad  y -> y.Value

f()
f()

输出

Big bad side-effect to let us know it's a first time
val it : int = 2

> 
val it : int = 2