在 C# (OOP) 中,我可以轻松地按需计算值并记住计算后的值(也称为缓存),例如
class WorkItem {
private Dictionary<string, object> myFields;
private WorkItemState myState;
// ...
public WorkItemState State {
get {
if (myState == null) {
myState = ParseWorkItemState(myFields["State"]);
}
}
}
}
或者我也可以使用
Lazy<T>
。
我可以使用类和可变字段在 F# 中模拟相同的 OOP 风格方法,或者 可能又是“懒惰”的表达方式,但更“惯用的 FP”方法是什么?
简短的回答是:与 C# 中的相同。
在函数式编程 (FP) 中,您始终可以使用 State Monad 来“模拟”状态突变,因此一种选择是传递状态:
let doSomething myFields myState =
match myState with
| Some s -> "foo", s
| None -> "bar", parseWorkItemState (myFields["State"])
虽然“实用”,但人们可能会争论这是否真的有必要。
如果我们假设你想要记忆的状态是不可变的,并且计算它的函数是参照透明,那么是否记忆可变字段中的值就不那么重要了。
归根结底,这是一个关于 FP 到底是什么的半哲学问题。在我看来,这与引用透明度有关。如果您使用可变字段记忆引用透明函数,它仍然保持引用透明。 懒惰
lazy
计算,就像在 C# 中一样。然而,要求是记忆的函数是引用透明的。
如果不是,那么你所做的一切都不会起作用根据评论,我提出了的以下变体:
let memoize (dict : System.Collections.Generic.IDictionary<_, _>) fn x =
match dict.TryGetValue x with
| true, v -> v
| false, _ ->
let v = fn (x)
dict.Add(x,v)
v
它不是使用“全局”字典来存储记忆值,而是通过传入的字典来结束。人们可以通过部分应用它来创建记忆函数:
open System.Collections.Generic
let f = memoize (Dictionary<int, int> ()) (fun n -> n*n)
记忆函数
f
关闭字典,它超出范围并与
f
本身一起被垃圾收集。您可以传递 f
,字典也随之而来。
这在概念上非常符合“闭包是对象,对象是闭包”的认识。