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

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

0
core.cache

我在使用core.cache,特别是clojure.core.cache.wrapped命名空间。

我发现即使解引用了该命名空间工厂提供的原子之一,获得的映射也不会=一个看起来等效的映射

(= {1 1} @(clojure.core.cache.wrapped/lru-cache-factory {1 1})) ;; => false

原因似乎是因为defcache LRUCache(这本质上是一个deftype)没有实现等价性。

这可以被修复吗?

1 答案

+1

选中
 
最佳答案

这不是一个错误。缓存可能会打印为散列映射,但这不是一个散列映射。

可以比较同类型的两个缓存以检查它们是否相等(但无法比较不同类型的两个缓存),如https://clojure.org/guides/equality中所述,因为缓存实现了clojure.lang.IPersistentCollectionequiv方法。

您可以通过互操作访问缓存内部的底层基本字段,但您将依赖于一个实现细节(字段名称).
user=> (= {1 1} (.cache @(clojure.core.cache.wrapped/lru-cache-factory {1 1}))) true

by
我对使用案例很感兴趣。缓存可以减轻程序处理它(按设计)不感兴趣的细节。但另一方面,……?
by
感谢你的回答。但仍然觉得有点不大对劲,因为在我的源代码段中,如果你改变 `=` 操作符的顺序(例如,计算 `(= @(clojure.core.cache.wrapped/lru-cache-factory {1 1}) {1 1})`),那么结果将会是 `true`。你不会希望 `=` 的结果取决于操作数放在左边还是右边吧?
by
缓存API是很特别的。由于它们是不可变的缓存,任何通常会改变可变缓存中某些元数据(TTL、使用数据等)的操作必须返回缓存的新实例。

除了实际的缓存API之外,还构建了一个类似于“映射”的API,以便可以使用Clojure的get、assoc、dissoc操作,但是这个外观确实有一些奇怪的边缘情况,例如在检查项目是否在缓存中以及随后查找(并发现它已“消失”)之间存在竞争条件,尽管缓存本身是不可变的。这给流式消费者带来问题,如core.memoize,它必须包含spin/retry逻辑来避免错误地返回nil。

这就是为什么我添加了用于提供更直观的“可变缓存”的包装命名空间(通过在不可变缓存中包装原子),但这些缓存的“安全”API并不真正是“映射”式的:lookup-or-miss是该API中最重要的函数,它能够避免竞争条件和反复执行,同时在需要时能够按需查找可以(重新)计算所需值。

能够在创建后使用新的哈希表值“种子”缓存,这是一个内在的变异操作,就像“逐出”一样。具有“类似映射”的get操作是一种便利,但只有当你愿意为已过期的请求缓存条目得到nil(或“缺少”值)时,这种便利才是有用的。

如README笔记中所述:“core.cache API难以正确使用。”,并链接到这篇文章https://dev.to/dpsutton/exploring-the-core-cache-api-57al,讨论了将缓存视为“仅仅是一个映射”所导致的现实世界中的错误。
by
@vemv 关于切换参数的顺序——这就像在两个事物上执行集合运算,其中只有一个实际是集合时会出现的情况:按照一个顺序尝试它(意外地)工作,按照另一个顺序尝试它失败(因为你在对非集合进行集合操作)。

仔细回顾源代码后,我认为我上面的答案在细节上不正确,但总体上仍然是正确的,即Clojure声称只有当两个deftype实例是同一类型时,它们才是相等的。

如我长篇回复@pbwolf所示,core.cache的“映射类似”API是有泄漏的,并且会引导人们用它做错事:
by {用户名}
谢谢!这些是有价值的见解。

因此,我最终实现了一个包装器,以确保只使用正确的方法,并且只能以安全的方式访问底层数据。

仅仅作为一个观察,我想知道将deftype改为defrecord是否会有所帮助?
...