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)

执行错误 (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")` 是特殊的,其行为应被视为未定义
by
足够公平 :) 我想这种困惑的部分原因在于

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

原始问题出现在一些运行 postwalk 并在每个形式上调用关键字-fn 的代码中,这对于大多数数据结构(如字符串)来说工作良好。
by
也许对这些情况更好的词是 “未定义”
by
我最近在这个代码库中遇到了这个问题。我在我们的工作代码库中创建了一个只包含数字作为键的排序映射表;在正常测试中工作良好,但在类型检查(如 `(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)
执行错误(ClassCastException)在 java.lang.Long/compareTo (Long.java:59)。
无法将 java.lang.String 类型转换为 java.lang.Long 类型(java.lang.String 和 java.lang.Long 都在模块 java.base 中加载)
```
by
如果你尝试在真正的Java中这样做,编译器会阻止你。

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