请分享您的想法,参加2024年Clojure调查问卷!

欢迎!请查看关于页面,了解更多此网站工作方式的信息。

+2 投票
Collections
编辑

我在sorted-set中发现了一些有趣的行为。例如,对于正常的set,当元素不存在时返回nil。然而,对于sorted-set,如果比较器没有友好地处理类型,则调用会抛出异常。这个原因是合理的(比较器需要能够正常比较元素),但由于我们知道如果元素的类型无法通过默认方式比较,则元素不在集合中,所以“不存在成员”的情况下的接口稍显困惑。

换句话说,我认为,对于使用默认比较器的sorted-set,应该在无法比较类型时返回nil而不是抛出异常,以便允许nil技术。

例如,这个问题发生在将表单传递给关键字函数(:foobar form)时,该函数允许对许多其他集合进行nil技术。以下是一个小片段

`
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-initialized59552761865518221.clj:8123)。
类java.lang.String无法转换为类clojure.lang.Keyword(java.lang.String位于模块java.base的加载器中;clojure.lang.Keyword位于未命名的模块加载器中)
`

我认为我处理这种问题的选择是try/catch或编写自定义比较器,两者似乎都有点奇怪:)

我可能遗漏了什么,但仍然认为这是有趣的:)

1 答案

+2 投票

选中
 
最佳答案

如果你有一个字符串的有序集合,询问它是否包含一个关键字是一个特殊情况。这甚至意味着什么呢?

如果你想允许这种容错,你需要提供一个比较器,它可以在更广泛的事物集合中提供排序顺序。

感谢您的回复!还有一些后续问题

  - 为什么检查关键字是否包含在字符串的有序集合中比检查包含在普通集合中更为特殊?(不管实现排序需要比较,主要从API角度好奇为什么检查有序集合中的成员资格被认为是特殊的,但普通情况下不是。)

  - 关于这个问题,为什么 `(:foo "bar")` 不特殊?这个调用究竟意味着什么?

基本上,在我看来的情况下,尽管这些操作实际上没有意义,但许多东西却能优雅地处理 `(:foo coll)`,但它们不会抛出异常,而是高兴地返回 `nil`。 :)
因为有序意味着你有一个比较器,它对你能排序的内容有一些隐含的预期。

`(:foo "bar")` 是特殊的,其行为应该被视为未定义。
公平!:) 我猜困惑的一部分是,

`(:foo "bar")` 是异常的,但不会抛出异常
`(:foo sorted-set-coll)` 是异常的,但会抛出异常

原始问题是由于某些代码在使用 postwalk 时,对每个表单调用关键字-fn 而发现的,这对大多数数据结构(如字符串)都是有效的。
也许这些情况的更好描述词是“未定义”
我最近在我们工作中遇到这个问题。我在我们的代码库中构建了一个只包含数字作为键的有序映射;在正常测试中一切正常,但在类型检查(如 `(protocol? my-map)`)时崩溃,有时在规范验证期间也会崩溃。为什么?因为这些检查会在映射中寻找特定的关键字,并尝试将关键字与数字进行比较,从而导致抛出异常。

对我来说,这从根本上违背了映射的意义。人们预期(而且 clojure.core 和 clojure.spec.alpha 也预期),如果值满足 `map?`,则可以对其调用关键字并获取值或 nil。

我看不出任何理由说明询问一个字符串的有序集合是否包含关键字是一个异常情况(甚至说是未定义的)。这个问题的答案是简单明了的——意思是“这个键是否在此集合中?”,答案是永不会是“是”。

尝试将关键字插入到数字的有序集合中会造成崩溃是有意义的。但从 API 角度来看,查询一个不存在成员时永远抛出异常是没有意义的。

根据我的记忆,Rich Hickey曾指出,强类型实际上并不能捕获任何严重的错误,只能捕获在非常基本的测试中能发现的错误。然而,有序映射的这一意外行为导致了在正常测试中未被捕获的严重错误,这在强类型语言中是根本不可能发生的。对我来说,这意味着Rich对类型系统的看法可能是错误的,或者有序映射/有序集的实现存在问题,我更倾向于后者。
by
请注意,(强类型的)Java也存在相同的行为,这是我们观察这一问题时的一部分依据。

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

由于Clojure通过使用类型转换绕过Java的强类型,这导致你在运行时获得了ClassCastException,所以它只允许你通过Clojure的Java互操作来运行该代码。
...