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

我对使用案例很感兴趣。缓存可以减轻程序处理那些(设计上)没有兴趣的细节的负担。但另一方面,...?
感谢您的回答。不过,我对此还有点疑惑,因为在我的原始代码片段中,如果你改变`=`运算符的顺序(例如,评估`(= @(clojure.core.cache.wrapped/lru-cache-factory {1 1}) {1 1})`),则结果将是`true`。你不希望`=`的结果取决于操作数置于左侧还是右侧吗?
缓存API有些特殊。由于它们是不可变的缓存,任何通常会更改可变缓存中一些元数据(TTL,使用数据等)的操作都必须返回缓存的新实例。

在实际缓存API之上,还构建了一个“类似map”的API,以便您可以使用Clojure的get,assoc,dissoc操作在它们上操作。但是这个门面可以(并且确实)有一些奇怪的边缘情况,例如检查一个项目是否在缓存中然后再查找它(发现它已经“消失了”)——即使缓存本身是不可变的。这给上游消费者带来了问题,例如core.memoize必须包含spin/retry逻辑,以避免错误地返回nil。

这就是为什么我添加了提供更直观的“可变缓存”的 wrapped 命名空间(通过将不可变缓存包装在一个atom中),但这些缓存的安全API并不是真正的“类似map”:lookup-or-miss 是最安全的函数,它提供了一种避免竞争条件和重复执行的方式,同时仍然可以进行按需查找,如果需要,可以(重新)计算所需值。

在创建后使用新的值哈希表“初始化”缓存或“淘汰”操作固有地是修改操作。有一个类似map的get操作是一种便利,但只有当你乐于得到nil(或“缺失”值)时,如果你请求的缓存条目已过期。

如readme中所述:“core.cache API很难正确使用。”并且链接到了这个文章,该文章讨论了将缓存当作“只是映射”来处理所产生的现场bug。
by
@vemv 关于参数顺序的切换--这和尝试对两个事物进行集合运算一样,但它们中只有一个实际上是集合:尝试一种顺序,它可能意外地工作,尝试另一种顺序则失败(因为你对一个非集合进行了集合操作)。

回过头来更深入地查看源代码,我认为我上面的回答在细节上是不正确的,但整体上我还是正确的,即Clojure指出,两个deftype实例只有在它们是相同的类型时才相等。

正如我对@pbwolf的长篇回复所指出的,core.cache的“类似映射”API是有漏洞的,并导致人们错误地使用它 :|
by
谢谢!这些是很有用的见解。

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

仅仅作为一个观察,我怀疑将deftype→defrecord改变是否会在这里有所帮助?
...