Lazy<_> 和 thunk (fun () -> ...) 之间的实际区别

Practical differences between Lazy<_> and thunk (fun () -> ...)

在 F# 核心库中,有采用 thunk (fun () -> ...) 的高阶函数,但在概念上也可以采用 Lazy<_>,例如 Option.defaultWith。 F# 通过 lazy 关键字对 Lazy<_> 提供了良好的语法支持,但我想不出 F# 核心库中有任何函数采用 Lazy<_> 而不是 thunk。我猜这是因为 "more FP" 使用 thunk 而不是 .NET 特定的 Lazy<_> 类型。

但除了模糊之外 "less FP":如果最多需要 1 次该值,那么使用 Lazy<_> 而不是 thunks 的实际考虑是什么?例如,是否存在性能差异 (CPU and/or allocations/memory)?其他问题? Lazy<_> 比 thunk 更能解决哪些情况?

作为包装 thunk 的单独对象,提供同步并保存结果引用,Lazy<_> 与简单的 thunk 相比有一些额外的开销。

如果您知道您只会(最多)评估一次 thunk,我认为如果可以的话您没有理由不使用函数。您可以在另一个函数中包装对 Lazy<_>Value 的调用,但在这种情况下,据我所知它没有任何好处。

我记得 Lazy<_> 有用的一个场景是当我们在产品中有两个单独的功能标志时,如果启用,这两个功能标志都可能需要初始化 Orleankka actor 系统 - 昂贵的操作,每个应用程序只能执行一次启动。

所以我们的选项要么是很多嵌套的 ifs,可变选项,要么是这个:

let actorSystem = lazy initializeActorSystem ()

if feature1Enabled then
    let as = actorSystem.Value
    ...

if feature2Enabled then
    let as = actorSystem.Value
    ...

与 thunk 不同,Lazy<_> 还允许您检查 thunk 是否已被评估。来自同一个例子:

if actorSystem.IsValueCreated then
    actorSystem.Value.Dispose()

其他情况通常是当您不确定是否最多评估一次 thunk 时,尤其是当它可以同时发生时。我认为我们也有这样的用例,当没有在本地提供给同时启动的几个组件时读取一些外部配置 - Lazy<_> 确保我们只进行一次远程调用(如果需要的话),而不管哪个组件首先发现它需要它。