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

欢迎!请参阅关于页面,了解更多此站点如何运作的信息。

+2
集合
编辑于

我最近遇到一些关于sorted-set的有趣行为。例如,对于正常set的用户合约是当元素不存在时返回nil。但是,对于sorted-set,如果在比较器中不优雅地处理类型,则调用会抛出异常。这个原因是合理的(比较器需要能够合理地比较元素),但是由于我们知道如果元素类型无法用默认方式进行比较,则元素不在集合中,所以在“无成员”的情况下该接口有点令人困惑。

换句话说,我猜想我建议当使用默认比较器的排序集合无法进行比较时,应该使用合理的默认行为 nil 而不是 throw,以便允许空值 punning。

例如,在将表单传递给关键字函数 (:foobar form) 时遇到了这种情况,这允许对许多其他集合进行空值 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)在用户 eval246355 (form-init59552761865518221.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 的代码中,这在大多数数据结构(如字符串)中工作正常。
by
这些情况中一个更好的词可能是 "未定义的"。
by
我刚才在我们工作的代码库中遇到了这个问题。我创建了一个只包含数字作为键的有序映射,在正常测试中工作正常,但在类型检查如 `(protocol? my-map)` 以及有时在规格验证期间崩溃。为什么?因为这些检查会在映射中寻找某些关键字的 existence,这样会抛出异常,因为它试图将关键字与数字进行比较。

这基本上打破了地图的本质契约。人们期望(实际上 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)
在java.lang.Long/compareTo (Long.java:59)处执行错误(ClassCastException)。
无法将java.lang.String转换为java.lang.Long(java.lang.String和java.lang.Long处于加载器'bootstrap'的java.base模块中)。
```
如果您在真实的Java中尝试这样做,编译器会阻止您。

它让您可以通过Clojure的Java互操作运行该代码,因为这允许Clojure绕过Java的强类型使用类型转换,这也是为什么您在运行时得到ClassCastException的原因。
...