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

selected
 
最佳答案

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

可以比较相同类型两个缓存以相等性进行比较(但不能比较不同类型的两个缓存)-- 根据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,其中讨论了将缓存当作“仅仅是映射”来对待所导致的野外错误。
@vemv 关于参数顺序切换的问题——这与尝试对两个事物执行集操作(而其中只有一个实际上是集合)时遇到的情况相同:试着以一个顺序尝试,它(意外地)就成功了,试着以另一个顺序尝试,它就失败了(因为你对一个非集合执行了集合操作)。

仔细回顾源代码后,我认为我上面的回答在细节上是错误的,但在整体意义上仍然是正确的:Clojure表明,只有在两个deftype实例相同类型时它们才是相等的。

正如我对@pbwolf的更长回复所述,core.cache的“类似映射”的API有漏洞,并导致人们用它做错事 :|
谢谢!这些是很有价值的见解。

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

仅仅作为一个观察,我想知道是否将deftype->defrecord的修改可以帮助到这里?
...