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

欢迎!请参见关于页面以了解更多关于其工作方式的详情。

+2
集合
编辑

刚刚遇到了一些关于sorted-set的有趣行为。具体来说,对于普通set,当元素不存在时,用户协议为nil。然而,对于sorted-set,如果比较器没有优雅地处理类型,调用将抛出异常。这个原因是合理的(比较器要求能够理智地比较元素),但在“非成员”的情况下接口略显困惑,因为我们知道如果类型的默认比较方式不能使用,该元素就不存在于集合中。

换句话说,我猜我建议的是,使用默认比较器的sorted-set的合理默认行为应该是当类型不能比较时应返回nil而不是throw,以便允许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)在user/eval246355(form-init59552761865518221.clj:8123)。
java.lang.String无法转换成class clojure.lang.Keyword(java.lang.String在模块java.base中由加载器'bootstrap'; clojure.lang.Keyword在未命名的模块中由加载器'app'加载)
`

我认为我处理这种情况的选择可能是try/catch或者编写自定义比较器,两者似乎都有点奇怪 :)

我可能遗漏了什么,但仍然觉得这很有趣 :)

1 个答案

+2

选中
 
最佳答案

如果您有一个字符串排序集,询问它是否包含一个关键字是一个非常特殊的情况。这又意味着什么呢?

如果您要对此做出容忍,则需要提供一个提供更广泛排序顺序的比较器。

感谢回复!几个后续问题

  - 为什么检查关键字是否在 _已排序_ 的字符串集中比在 _正常_ 的字符串集中更为特殊?(无论实现排序的需要比较的事实,主要是从 API 视角来看,为什么检查 _排序_ 的情况被视为特殊,而正常情况不是。)

  - 就此而言,为什么 `(:foo "bar")` 不特殊?这个调用意味着什么?

基本上,我觉得许多事情优雅地处理 `(:foo coll)`,尽管这实际上并没有什么意义,但它们不抛出异常,而是高兴地返回 `nil`。 :)
因为排序意味着您有一个比较器,它有隐含的期望,即您能够排序什么。

`(:foo "bar")` 是特殊的,其行为应被视为未定义
足够公平 :) 我认为部分困惑在于

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

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

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