2024 年 Clojure 状态调查 中分享您的想法!

欢迎!请参阅关于页面,了解有关该功能的一些更多信息。

+2
集合
编辑

最近遇到了一些关于 sorted-set 的有趣行为。具体来说,对于普通 set,用户合约是当元素不存在时返回 nil。然而,在 sorted-set 中,如果比较器不能优雅地处理类型,调用将会抛出错误。这个 原因 看起来很合理(比较器需要能够合理地比较元素),但接口在“无成员”情况下有些令人困惑,因为如果我们知道元素不能通过默认的比较方式进行比较,则我们知道元素不在集合中。

换句话说,我猜我提议对于使用默认比较器的排序集合,当类型不能比较时,合理的默认行为应该是 nil 而不是 throw,以便允许 nil-punning。

例如,当将项传递给关键字函数 (:foobar form) 时遇到了这种情况,它允许在许多其他集合上进行 nil-punning。这里是一个小的代码片段

`
user> (def normal #{"0000"})

;; => #'user/normal

user> (def sorted-version (sorted-set "0000"))

;; => #'user/sorted-version

user> normal

;; => #{"0000"}

user> sorted-version

;; => #{"0000"}

user> (:foobar normal)

;; => nil

user> (:foobar sorted-version)

执行错误(ClassCastException)在 user/eval246355(form-init59552761865518221.clj:8123)处。
class java.lang.String 不能转换为 class clojure.lang.Keyword (java.lang.String 位于模块 java.base 的加载器 'bootstrap' 中; clojure.lang.Keyword 位于加载器 'app' 的未命名模块中)
`

我认为我处理此问题的选择是 try/catch 或编写自定义比较器,这两个选择都有些不寻常 :)

我可能遗漏了一些东西,但无论如何这很有趣 :)

1个答案

+2

选中
 
最佳答案

若字符串集合已排序,询问是否包含关键词就是一种特殊情况。这究竟是什么意思?

如果您想对此有所容忍,您需要提供一个比较器,以便对更广范围内的你可能传递给它的内容进行排序。

感谢您的回复!几个后续问题

  - 为什么在 _有序_ 字符串集合中检查某个关键词的存在比在 _常规_ 字符串集合中检查它更特殊?(不管实现排序是否需要比较,我只是从 API 角度好奇为什么它被认为是 _排序_ 中的特殊情况,而不是常规情况)。

  - 此外,为什么 `(:foo "bar")` 不是特殊情况?这次调用应该代表什么意思?

总的来说,我觉得许多东西优雅地处理了 `(:foo coll)`,虽然它们在现实中并不构成意义,但它们不会抛出异常,并高兴地返回 `nil`。 :)
因为排序意味着你有一个比较器,它有你能够排序的隐含期望。

`(:foo "bar")` 是一个特殊情况,其行为应被考虑为未定义。
公平得体 :) 我认为混淆的部分可能是,

`(:foo "bar")` 是一个特例,但它不会抛出异常。
`(:foo sorted-set-coll)` 是一个特例,但确实会抛出异常。

原始问题是从一些代码中发现的,这些代码在 postwalk 中运行,对每个表单调用关键字-fn,这对于大多数数据结构(如字符串)来说运行良好。
by
对于这些情况,也许一个更好的词是 "未定义"。
by
我刚刚在工作中遇到的这个问题咬了我一口。我构建了一个只包含数字作为键的排序映射;它在进行正常测试时运行良好,但在类型检查(如 `(protocol? my-map)`)和有时在进行规范验证时崩溃。为什么?因为这会检查映射中是否存在某些关键字,这样做会抛出异常,因为它试图将关键字与数字进行比较。

对我来说,这从根本上破坏了成为映射意味着什么的基本契约。人们期望(事实上 clojure.core 和 clojure.spec.alpha 也期望),如果一个值满足 `map?`,那么可以调用它的关键字并获取一个值或 nil。

我看不到任何理由声称询问一个字符串排序集合是否包含一个关键字是一个异常(甚至未定义)的情况。对这个问题的回答 "这究竟意味着什么?" 是简单的 - 这意味着 "这个键在这个集合中吗?",答案是绝不会的。在数学中,我可以询问一个虚数是否在有序整数集合中,尽管它们是不可比较的,但答案并不是未定义的,只是不。

尝试将关键字插入数字排序集合中应该引发崩溃是有意义的。但从 API 角度来看,查询不存在成员从不抛出异常是没有意义的。

IIRC Rich Hickey 指出,强类型实际上不会捕获任何严重的错误,只是你在非常基本的测试中会发现的那种错误。然而,排序映射的这种意外行为导致了在正常测试期间未捕获的严重错误,而且在一个强类型语言中是不可能的。对我来说这意味着 Rich 关于类型系统的说法是错误的,或者 sorted-map/sorted-set 的实现有误,我更倾向于后者。
注意,(强类型) Java 也有相同的行为,这是我们在查看这个时注意到的。

```
user=> (def m (java.util.TreeMap.))
(quote user/m)
user=> (.put m "a" "b")
nil
user=> (.containsKey m 100)
在 java.lang.Long/compareTo (Long.java:59) 处执行错误 (ClassCastException)。
无法将 java.lang.String 转换为 class java.lang.Long(java.lang.String 和 java.lang.Long 在模块 java.base 的引导程序中)
```
如果你在真实的 Java 中尝试这样做,编译器会阻止你。

它允许你通过 Clojure 的 Java 交互运行代码,因为 Clojure 使用类型转换绕过 Java 的强类型,这就是为什么你在运行时得到 ClassCastException 的原因。
...