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

欢迎!请参阅关于页面了解更多有关该工具的信息。

+2 投票
集合
编辑

我遇到了一些有趣的 sorted-set 行为。具体来说,对于常规 set 的用户协议是在元素不存在时返回 nil。然而,对于 sorted-set,如果比较器没有优雅地处理类型,调用会抛出异常。这种行为的原因是可以理解的(比较器需要能够合理地比较元素),但在“不包含”的情况下,由于我们知道如果元素的类型不能默认比较,则该元素就不存在于集合中,因此接口有些令人困惑。

换句话说,我认为我提出的是,对于使用默认比较器的有序集合,一个合理的默认行为是当类型无法比较时应返回 nil,而不是 throw,以便允许 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)在用户/评估246355(from-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)`)以及有时在规范验证期间崩溃。为什么?因为这些检查会检查映射中是否存在某些关键字,并且尝试这样做会抛出异常,因为它试图将一个关键字与一个数字进行比较。

对我来说,这从根本上破坏了作为映射含义的契约。人们预期(事实上 clojure.core 和 clojure.spec.alpha 也预期),如果值满足 `map?`,则可以在其上调用关键字并得到一个值或一个 nil。

我看不出询问一个字符串有序集中的关键字是否包含异常(甚至未定义)的任何理由。对这个“是什么意思?”这个问题的答案是简单的——“这个键在这个集合中吗?”答案是肯定的。在数学中,我可以问一个虚数是否在有序整数集中,尽管它们是不可比较的,但答案不是未定义的,只是没有。

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

IIRC Rich Hickey 指出,强类型实际上不会捕获任何严重的错误,只是在非常基本的测试中会找到。然而,有序映射的这种意外的行为导致了一个在正常测试期间未被捕获的严重错误,并且在一个强类型语言中不可能实现。对我来说,这意味着要么 Rich 对类型系统是错误的,要么有序映射/有序集的实现是有缺陷的,我更倾向于后者。
请注意,(强类型)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)。
类java.lang.String无法被转换为类java.lang.Long(java.lang.String和java.lang.Long位于模块java.base的加载器'bootstrap'中)。
```
如果您真的在Java中尝试这样做,编译器将阻止您。

它之所以允许您通过Clojure的Java互操作运行该代码,仅仅是因为Clojure通过类型转换绕过了Java的强类型检查,这就是为什么您会在运行时得到ClassCastException的原因。
...