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) 相等。这允许调用者将恰好所需的工作量放入 calc,这样预计会比 A 计算得更快。

Spoof 返回 calc 返回的内容,或在缓存命中时返回存储在 args 的缓存内容。换句话说,如果之前已经调用过 (A args),那么在调用 (spoof A args calc) 之前不会调用 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 (link: arg1] (fn [) "calc for (link: arg1)")) ;; 注意如何通过那个功能轻松实现自定义备忘录器!

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

它还包含 .put,用于处理非同步情况。

0

评论者:seancorfield

很有趣。我的下一个问题将是:“你是怎么想到这个的?”

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

我需要考虑一下这个问题。缓存本身是与 {{build-memoizer}} 返回的函数对象的元数据相连接的,所以,技术上,如果你自己想要编程,所有这些都已经公开了。

0

评论由:lgs32a 提出

我的理解是,我需要通过带有修补过的(忽略参数)fn 的 *with* 来调用。

真的不能那样调用公开的函数哦。

0

评论由:lgs32a 提出

我还需要复制并粘贴在 209 行之后的代码来处理罕见的定时问题。

0

评论由:lgs32a 提出

或者我不理解这个意思,你建议我编写一个补丁吗?

0

评论者:seancorfield

我的意思是:如果你想检查一个参数列表键是否存在并且/或者通过上面的thunk更新缓存,原始缓存就直接在元数据中。你可以直接在缓存上调用{{core.cache/through}}(或者更可能的是{{core.cache/through-cache}}),使用一个thunk(即使只是有一个参数,你也可以简单地忽略它)

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

任何使用TTL缓存的用户都必须处理在访问过程中的淘汰边缘情况(除非他们不关心返回{{nil}}),所以,是的,上面提到的{{spoof}}函数可能会需要重试thunk调用 -- 你是否在那个点更新缓存取决于你(即,如果你的{{spoof}}调用期间发生了缓存淘汰,后续调用应该有什么行为?)。

关键是,{{spoof}}需要实际的缓存来完成它的任务。被记忆化的函数只能通过闭包的方式访问该缓存,所以{{spoof}}必须操作记忆化函数所在的{{Var}}。

这解释了你建议的吗?

0

评论由:lgs32a 提出

Spoof 还会利用 d-lay 中的内部 through* 作为其锁定设施。它可能需要微小的调整以支持thunk。

上面推荐的设计故意使用了记忆化的函数,以便提取 ::cache。

关于TTL修复,我需要再次查看它。如果您对带有 spoof/spoof-aysnc 的补丁感兴趣,请告知。

0

评论者:seancorfield

我讨厌这些名字,但是我考虑为允许用户更容易地从库外构建自己的版本而暴露足够机器的结构编写补丁。

0

评论由:lgs32a 提出

选择名称以适应当前库的设计。Guava 为您提供强大的原语,并声称具有通用低级名称,而这个库的设计风格则是基于对用户如何使用它的强假设。这使得您可以立即拥有一个带有缓存支持的备忘函数。

这就是为什么我只选择仅描述您为什么要使用它们的名称/此外/。可能有一个比“欺骗”更好的词 - 我不是母语者。

core.cache 仅暴露缓存的数据结构,但它并没有像 Guava 那样暴露一个合适的引用类型来缓存计算(而不是值)。我怀疑 core.cache 的作者没有做到这一点是因为他们认为 Clojure 的引用类型已经足够。core.memoize 作为内部实现,为自身实现了一个这样的引用类型。我不确定是否在库中暴露这种功能(在备忘功能之外也有大量用例),这个功能是否应该放在这个库的正确位置。因此,命名和围绕备忘功能进行包装,以清楚地表明该功能存在于备忘的范围内。

0
参考:[https://clojure.atlassian.net/browse/CMEMOIZE-24](https://clojure.atlassian.net/browse/CMEMOIZE-24)(由 lgs32a 报告)
...