如何在 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"
,您实际上已经记住了结果。如果 getSomething
是 referentially 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
我有一个区分联合 ("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"
,您实际上已经记住了结果。如果 getSomething
是 referentially 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