请在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方法。

您可以通过交互操作来访问缓存内部的底层基础字段,但您将依赖于实现细节(字段名)。
用户=> (= {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)带来了问题,core.memoize必须包含自旋/重试逻辑,以避免错误地返回nil。

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

在创建后能够“播种”缓存,即使用新的值哈希表是一个本质上是变异的操作,正如“驱逐”操作。有一个类似映射的get操作是一个便利之处,但只有当你愿意在请求的缓存条目已过期时得到nil(或“丢失”值)时,这才是个便利之处。

正如说明书中指出的:“核心缓存API难以正确使用。”它链接到https://dev.to/dpsutton/exploring-the-core-cache-api-57al,该文档讨论了因将缓存视为“仅仅是映射”而导致的野外错误。
by
@vemv:关于参数顺序交换——正如你在尝试对两个事物(其中只有一个实际上是集合)进行集合操作时看到的那样:尝试一种顺序它会意外地工作,尝试另一种顺序它会失败(因为你在非集合上执行了集合操作)。

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

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

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

仅作为一个观察,我想知道如果将deftype->defrecord改为defrecord会有帮助吗?
...