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

欢迎!请查看关于页面,了解更多关于此功能的信息。

+2
集合
编辑

最近在使用sorted-set时遇到了一些有趣的行为。例如,对于正常set,当元素不存在时,其合约返回nil。然而,对于sorted-set,如果比较器无法优雅地处理类型,则调用将引发异常。这种原因是有意义的(比较器需要具有合理比较元素的能力),但在“非成员”情况下,接口有些令人困惑,因为我们知道如果元素的类型不能默认进行比较,那么该元素不会在集合中。

换句话说,我建议对于使用默认比较器的排序集合而言,应该有一个合理的默认行为:当类型无法比较时返回nil而不是抛出异常,以允许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)

执行错误(类转换异常)在 user/eval246355 (form-init59552761865518221.clj:8123)。
类 java.lang.String 无法转换到类 clojure.lang.Keyword (java.lang.String 位于模块 java.base 的加载器 'bootstrap' 中;clojure.lang.Keyword 位于未命名的模块的加载器 'app' 中)
`

我认为我处理这种情况的选择是 try/catch 或编写自定义比较器,但这两种方法似乎都有些糟糕 :)

我可能遗漏了某些东西,但仍然觉得这很有趣 :)

1 答案

+2
by
选中 by
 
最佳答案

如果你有一个排序后的字符串集合,询问它是否包含一个关键字是一个异常情况。这又是什么意思呢?

如果你想要对这个异常情况有容错性,你需要提供一个比较器,它可以提供比你可能传入的更广泛的集合的排序顺序。

by
感谢您的回复!以下是一些后续问题:

  - 为什么检查关键字是否在排序后的字符串集合中比检查在正常集合中更具有异常性质?(不管排序的实现需要比较的事实,但从API的角度来看,为什么在排序的情况下检查成员资格被认为是异常的,而在正常情况下不是)。

  - 关于这个问题,为什么 `(:foo "bar")` 不是异常的?这个调用有什么意思?

基本上,在我看来,尽管它们在逻辑上并不合理,但许多东西都可以优雅地处理 `(:foo coll)`,但它们不会抛出异常,并且高兴地返回 `nil`。 :)
by
因为排序意味着你有比较器,它隐含地期望你能够对什么进行排序。

`(:foo "bar")` 是异常的,其行为应视为未定义
by
足够公平 :) 我猜困惑的一部分是因为

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

原始问题是从一些运行 postwalk 并在每种形式上调用关键字函数的代码中发现的,这对大多数数据结构(如字符串)来说是正常工作的。
或许用“未定义”这个词来形容这些情况更恰当
我最近在工作中遇到了这个问题的麻烦。我在系统中构建了一个只包含数字作为键的排序映射,它在正常测试中运行良好,但在类型检查(如 `(protocol? my-map)`)和有时在规范验证中崩溃。为什么?因为这些检查会检查映射中是否存在某些关键字,而这样做会抛出异常,因为它尝试将一个关键字与一个数字进行比较。

在我看来,这从根本上打破了映射的含义。人们期望(实际上 clojure.core 和 clojure.spec.alpha 也期望),如果一个值满足 `map?`,则可以对其调用关键字,要么得到一个值,要么得到一个 nil。

我看不出来为什么询问一个字符串排序集是否包含一个关键字是异常的(甚至未定义的情况)。对这个“那是什么意思?”的答案很简单 - 它的意思是“这个键在这个集合中吗?”,答案总是不。

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

IIRC Rich Hickey 指出,强类型实际上不捕获任何严重的错误,只有那些你在非常基本的测试中能找到的错误。然而,排序映射的不寻常行为引起了一个严重的错误,这个错误在正常测试中没有被发现,在强类型语言中也是不可能出现的。对我来说,这意味着要么 Rich 关于类型系统的说法是错误的,要么排序映射/排序集的实现是错误的,我倾向于后者。
by
请注意,(强类型)Java也有相同的行为,这在一定程度上是我们在此关注的。

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

它让您通过Clojure的Java互操作运行代码,只是因为Clojure通过使用类型转换绕过了Java的强类型,这就是为什么您在运行时获得ClassCastException的原因。
...