2024 Clojure 状态调查中分享您的想法!

欢迎!请查看关于页面以了解更多有关如何使用本网站的信息。

0
core.memoize

问题:函数 A 已进行记忆化。函数 B(及其他操作)计算一个有效的 A 函数调用,而从未调用过 A。我们希望在 B 中使用这种效果,进一步减少 A 的计算量。如果我们期望 B 比 A 运行得快,我们也希望 A 的调用者等待相应的 B 正在运行时。

B 中实现这一点的期望操作方法是
(spoof A args calc)

其中 calc 是一个无参数的 fn,它产生一个等于 (apply A args) 的值。这允许调用者将正好等于他期望比 A 快的工作量放入 calc。

Spoof 返回 calc 返回的值或 cache 中为 args 存储的值。例如,如果在调用 (spoof A args calc) 之前发生了一个 (A args) 的调用,calc 将不会被调用。

在已经得到 (A args) 的结果且不希望被 (A args) 的并发调用阻塞的情况下,期望的操作是

(spoof-unsynchronized A args val)

它立即返回 val 并尽快更新 A 的缓存。由于非同步欺骗是一种非常快速的操作,它无论如何都会与 (A args) 的调用发生竞争条件,所以不需要阻塞 (A args) 的调用。 (因此,swap! 内部缓存状态就足够了)。

11 答案

0

评论由:seancorfield

这感觉不像应该是 core.memoize 库的一部分,但它应该可以在其之上构建类似的功能(并且目前还没有,对吗?)。

为了允许这样做,必要的最小修改是否是暴露底层的缓存(这样你可以直接对缓存调用 deref/swap!)?

0

评论人:lgs32a

对于不同步的情况是,对于同步(有趣)的情况,可能需要恶意的黑客攻击,我需要查看代码。

我不同意不应该内置此功能。这个功能相当通用,并允许非常灵活的用户定义的备忘录实现。

上述推荐的设计是剽窃自 Google Guavas 缓存。

https://google.github.io/guava/releases/23.0/api/docs/com/google/common/cache/Cache.html#get-K-java.util.concurrent.Callable-

它允许这样的备忘录。你只需调用
(.get c (链接:arg1] (函数 [] "为 (链接:arg1) 计算")) ;; 注意如何使用这个功能轻松实现自定义备忘录!

运行 fn synchronized (once) 的必要逻辑在我们的案例中通过 core.memoize 实现。所以如果 core.memoize 提供类似的设施,我相信那将是一个非常通用的 API,允许构建更多东西(自定义备忘录实现)。

它还有一个 .put,可以处理不同步的情况。

0

评论由:seancorfield

有趣。我的下一个问题本来是“你是从哪里得到这个想法的?”。

{{core.memoize}} 的底层机制与一个处理 thunk 等的 * } 函数类似工作,但 {{build-memoizer}} 函数封装了所有这些,以及 {{PluggableMemoizer}} 类型。

我必须考虑这一点。缓存本身附加到从 {{build-memoizer}} 返回的功能对象的元数据,因此,从技术上讲,如果自己编程,所有这些都已经暴露。

0

评论人:lgs32a

我的理解是,我需要使用修正过的 (args 忽略) fn 通过* 调用。

真的不能称之为公开 :)

0

评论人:lgs32a

我还需要复制粘贴从 209 行开始处理的代码,以解决一个罕见的时机问题。

0

评论人:lgs32a

或者,我是不是误解了你的意思,你建议我如果想要编程一个补丁的话?

0
by

评论由:seancorfield

我的意思是:如果需要检查是否存在参数列表键或通过您上面提到的延迟加载(thunk)更新缓存的新值,原始缓存就存储在元数据中。可以直接在该缓存上调用 {{core.cache/through}}(或者更可能是 {{core.cache/through-cache}})来操作该缓存,即使它只有一个参数(实际上可以忽略)。

(swap! (:clojure.core.memoize/cache (meta the-fn-var)) clojure.core.cache/through-cache [arg1] (fn [_] "为[arg1]计算"))

任何使用TTL缓存的用户都必须处理访问时可能发生的剔除边缘情况(除非他们不在意返回 {{nil}}),所以,是的,执行上述 swap! 的 {{spoof}} 函数可能需要重试thunk调用——您是否在那个点更新缓存将取决于您(即,如果您在 {{spoof}} 调用期间发生缓存剔除,您希望后续调用有怎样的行为?)。

关键在于,{{spoof}} 需要实际的缓存来完成任务。缓存化的函数只能通过闭包访问该缓存,因此 {{spoof}} 必须在存储缓存化的函数的 {{Var}} 上操作。

这澄清了我建议的内容吗?

0
by

评论人:lgs32a

Spoof还会利用d-lay中的内部 through* 锁定功能。可能需要稍作调整来支持thunk。

上面推荐的设计故意采取缓存化的函数,以便提取 ::cache。

关于TTL修复,我需要再次审视它。如果您对 spoof/spoof-aysnc 修复感兴趣,请告诉我。

0
by

评论由:seancorfield

我讨厌这些名字,但我会考虑一个补丁,让用户更容易在库外构建自己的版本,同时暴露足够多的机制。

0
by

评论人:lgs32a

我根据当前库的设计选了这些名字,因为Guava给你提供了强大的原语和通用的底层名称,而此库的风格是做出关于用户将如何使用它的强假设。它立即让您拥有一个附加了缓存的缓存化函数。

这就是为什么我选了这些仅描述您用途原因的名字。可能会有比“spoof”更好的词 - 我不是母语人士。

core.cache只公开了缓存的数结构,但它没有像Guava那样公开适当的引用类型用于缓存计算(而不是值)。我怀疑core.cache的作者未做此事是因为他们认为Clojure的引用类型就足够了。core.memoize将自己实现为一个内部引用类型。我不确定将此功能公开是否适当,因为该功能在归一化之外还有很多用例。因此,命名和包装归一化函数,以清楚地表明该功能应在归一化范围内。

0
by
...