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

欢迎!有关如何工作的更多信息,请参阅关于页面。

0
core.memoize

问题:函数 A 被缓存。函数 B(及其他一些功能)计算 A 的有效函数调用,而从未调用过 A。我们希望在 B 中利用这个效果来进一步减少 A 的计算。如果我们期望 B 比 A 运行得更快,我们也希望 A 的调用者等待相应的 B 运行。

B 中实现此功能的期望操作
(spoof A args calc)

其中 calc 是一个零参数函数,它产生的值等于 (apply A args)。这允许调用者将恰好相当于 calc 预期比 A 更快执行的工作量投入到 calc 中。

Spoof 返回 calc 返回的值或者在某些情况下返回缓存给 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同步(一次)所需的基本逻辑在我们这个例子中是在core.memoize中实现的。所以如果core.memoize公开了一个类似的设施,我认为这将是一个非常通用的API,允许构建更多的东西(自定义记忆化器的实现)。

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

0

评论者:seancorfield

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

在 {{core.memoize}} 的底层机制与 * } 函数相似地处理thunk等,但 {{build-memoizer}} 函数封装了所有这些,还包括 {{PluggableMemoizer}} 类型。

我需要认真考虑一下。缓存本身附加到 {{build-memoizer}} 返回的函数对象的元数据上,所以,技术上只要你想自己编写程序,所有这些都是已经公开的。

0

评论作者:lgs32a

我理解,我可能需要使用一个修补过的(忽略参数)fn通过*私有地调用。

真的无法调用那个公开的 :)

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 [_] "为[arg1]计算"))

任何TTL缓存的用户都必须处理在访问期间的驱逐边缘情况(除非他们不在乎返回{{nil}}),所以,是的,一个执行上述swap!的{{spoof}}函数可能会在调用thunk时重试——你此时是否更新缓存取决于你(即,如果在{{spoof}}调用期间发生缓存驱逐,后续调用应该有什么行为?)。

关键是{{spoof}}需要实际的缓存来完成其工作。记忆化的函数只能通过闭包访问该缓存,因此{{spoof}}必须在持有记忆化函数的{{Var}}上操作。

这能解释我建议的内容吗?

0

评论作者:lgs32a

{{Spoof}}也会使用d-lay内部的通过*来利用其锁定设施。可能需要对其进行一些调整以支持thunk。

上述建议的设计故意采用记忆化函数以提取::cache。

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

0

评论者:seancorfield

我不喜欢这些名字,但如果补丁足以公开足够多的机制,使用户可以更容易地在外部构建自己的版本,我会考虑。

0

评论作者:lgs32a

我选择了这些名字,以适应库的当前设计。Guava提供了强大的原语,并声称具有通用的高级名称,而这个库以用户想要使用它的方式编写,它促使您立即具有附加缓存的记忆化函数。

这就是为什么我选择的名字仅描述了为什么你会使用它们/附加地/。可能比"spoof"这个词更好——我不是母语者。

core.cache仅公开缓存的数据结构,但它不公开用于简化计算(而不是值)的正确引用类型,如Guava所做。我怀疑core.cache的作者没有做这件事,因为他们认为Clojure的引用类型足够用。core.memoize为其自身实现了这样的引用类型作为内部实现。我不确定暴露这个功能(它有很多非.memoization用例)是否在此库中是合适的。因此,命名和将memoized函数包装起来,以清楚地表明功能仅限于memoization的范围。

0
by
...