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

欢迎!请查看关于页面以了解更多关于其工作方式的信息。

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

best answer by
 
最佳答案

这不是一个错误。缓存可能会打印为一个哈希表,但它不是一个哈希表。

同一类型的两个缓存可以比较其等价性(但不同类型的两个缓存不可以)——根据https://clojure.org/guides/equality,因为缓存实现了clojure.lang.IPersistentCollectionequiv方法。

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

我对使用场景很感兴趣。缓存减轻了程序对细节数据的兴趣(这是设计目的)。但是另一方面,...
感谢您的解答。但是,我的原始代码片段中,如果你改变 `=` 的顺序(例如,评估 `(= @(clojure.core.cache.wrapped/lru-cache-factory {1 1}) {1 1})`),那么你会得到 `true`。你不期望 `=` 的结果会因为哪一边的操作数而改变吗?
缓存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,讲述了将缓存视为“仅仅是映射”所导致的野外bug。
by
@vemv 关于改变参数顺序的问题——这是当你尝试对两个物品(其中只有一个实际上是集合)执行集合操作时会出现的情况:按一种顺序尝试它,它会意外地成功,按另一种顺序尝试它就失败了(因为你对一个非集合执行了集合操作)。

进一步回顾源代码后,我觉得我上面的回答在细节上是不正确的,但总体上仍然是正确的,因为Clojure说只有当两个deftype实例是同一类型时,它们才相等。

正如我稍长一点的回答对@pbwolf所表明的那样,core.cache的“类似映射”API是有漏洞的,它会导致人们错误地使用它:|
by
谢谢!这些是有用的见解。

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

仅作为观察,我想知道把deftype转为defrecord会有所帮助吗?
...