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

欢迎!请参阅关于页面以了解更多关于这个系统的工作方式的信息。

+2
集合
编辑

我遇到了一些有趣的sorted-set行为。例如,对于正常set的用户协议是当元素不存在时返回nil。然而,与sorted-set不同,如果比较器不能优雅地处理类型,调用将抛出异常。这个原因是有意义的(比较器要求能够健康地比较元素),但在“无成员”情况下,接口有点令人困惑,因为我们知道如果元素的类型不能通过默认比较,那么该元素就不存在于集合中。

换句话说,我猜测我正在提议,对于使用默认比较器的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)在user/eval246355(form-init59552761865518221.clj:8123)。
类java.lang.String无法转换为类clojure.lang.Keyword(java.lang.String位于模块java.base的加载器'bootstrap'中;clojure.lang.Keyword位于未命名的模块的加载器'app'中)
`

我认为我处理此问题的选项是尝试/捕获或者编写一个自定义比较器,两者都感觉有点奇怪 :)

我可能遗漏了某些东西,但是仍然觉得很有趣 :)

1回答

+2

选中
 
最佳答案

如果您有一个字符串的排序集合,询问它是否包含一个关键字是一个特例。这究竟是什么意思呢?

如果您想对此有容错性,您将需要提供一个比较器,它可以在更广泛的可能传递给它的事物集上提供排序顺序。

感谢您的回复!还有几个问题跟进

  - 为什么在 _排序_ 字符串集合中检查关键字是否存在比在 _普通_ 字符串集合中检查关键字更加特殊?(不管事实是排序的实现需要比较,我主要从 API 的角度好奇为什么检查 _排序_ 的情况被认为是特殊情况,而不是普通情况)。

  - 就此而言,为什么 `(:foo "bar")` 不是特殊的情况?这个调用有什么含义?

从本质上讲,好像很多人优雅地处理了 `(:foo coll)`,尽管它们实际上没有意义,但它们不会抛出异常,而且欣然返回 `nil`。 :)
因为排序意味着您有一个比较器,这个比较器对您可以排序的内容有隐含的期望。

`(:foo "bar")` 是特殊情况,其行为应该是未定义的
公平 enough :) 我想其中一部分困惑是

`(: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)
执行错误(ClassCastException)在 java.lang.Long/compareTo (Long.java:59)。
类 java.lang.String 无法被转换为类 java.lang.Long(java.lang.String 和 java.lang.Long 都在模块 java.base 中,由 'bootstrap' 加载器加载)
```
by
如果您在的真实Java中尝试这样做,编译器会阻止您。

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