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

欢迎!请看关于页面了解如何使用本站。

+2
集合
编辑

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

换句话说,我猜我是在建议,对于使用默认比较器的排序集,当类型无法比较时,应该有其合理的默认行为nil而不是抛出异常,以便允许使用nil进行pivot。

以示例来说,这种情况是在将形式传递到关键字函数(:foobar form)时遇到的,它允许大量其他集合使用pivot,以下是一个小例子

`
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)中
`

class java.lang.String不能转换成class clojure.lang.Keyword(java.lang.String位于模块java.base的loader'bootstrap'中;clojure.lang.Keyword位于loader'app'的匿名模块中)

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

1 答案

+2
by
已被选择 by
 
最佳答案

如果你有一个字符串排序集,询问它是否包含某个关键字是一个例外。这究竟是什么意思?

如果你想对此进行容错处理,你需要提供一个比较器,它能对更广泛的你可能会传入的项目集排序。

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

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

  - 关于这个问题,`(:(foo "bar")`为什么不是例外?这个调用意味着什么?

基本上,在我看来,许多东西已经优雅地处理了`(:foo coll)`,尽管它们在现实中并没有什么意义,但它们不会抛出异常,而是高兴地返回`nil`。 :)
by
因为排序意味着你有一个比较器,它隐式地认为你知道自己能对什么样的东西进行排序。

`(:foo "bar")` 是一个例外,其行为应该被视为未定义。
是的,确实如此 :) 我想困惑的部分在于

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

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

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

我看不出要求一个字符串的sorted-set包含关键字是什么异常(或者是未定义的)案例的理由。对“这是什么意思?”的回答很简单——这意味着“这个键是否在集合中?”答案总是会是否定的。在数学中,我可以要求一个虚数是否在有序整数集中,尽管它们不可比较,但答案不是未定义的,只是否定的。

尝试将一个关键字插入到数字的sorted-set中导致崩溃是有意义的。但从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都在loader 'bootstrap'的java.base模块中)
```
by
如果你在实际Java中尝试这样做,编译器将会阻止你。

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