请在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

被选为
 
最佳答案

这并不是一个错误。缓存可以像哈希映射那样打印,但它并不是一个哈希映射。

可以比较相同类型的两个缓存是否等价(但不能比较不同类型的两个缓存)——根据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。

这就是为什么我添加了包装名称空间,该名称空间提供了一种更直观的“可变缓存”(通过在不可变缓存周围包装一个atom),但这些缓存的“安全”API并不真正“映射类似”:lookup-or-miss是使用最安全的函数,提供了一种避免竞争条件和重复执行的方法,同时仍然可以在必要时按需查找请求的值。

在创建后在缓存中“播种”一个新的值哈希表是固有的更改操作,就像“驱逐”。有一个“映射类似”的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会有所帮助吗?
...