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

欢迎!请参阅关于页面获取有关这是如何工作的更多详细信息。

+2
Collections
找不到它是否被提出过,但看起来 {{empty?}}谓词对于临时集合是有损坏的


user=> (empty? (transient []))
IllegalArgumentException 不知如何从: clojure.lang.PersistentVector$TransientVector 中创建 ISeq:clojure.lang.RT.seqFrom (RT.java:528)

user=> (empty? (transient {}))
IllegalArgumentException 不知如何从: clojure.lang.PersistentArrayMap$TransientArrayMap 中创建 ISeq:clojure.lang.RT.seqFrom (RT.java:528)

user=> (empty? (transient #{}))
IllegalArgumentException 不知如何从: clojure.lang.PersistentHashSet$TransientHashSet 中创建 ISeq:clojure.lang.RT.seqFrom (RT.java:528)


解决方法是使用 {{(zero? (count (transient ...)))}} 检查代替。

*原因:* {{empty?}} 是基于可序列性,而临时并不实现。

*建议:* 在 {{empty?}} 中为计数的集合(counted? colls)添加一个分支。Temporary 实现了 Counted,因此通过此分支支持。其他计数的集合更快。序列分支继续在序列上工作。

性能测试


(def p [])
(def p1 [1])
(def t (transient []))
(def t1 (transient [1]))

;; 记录所有这些的最后时间
(dotimes [i 20] (time (dotimes [_ 10000] (empty? p))))
(dotimes [i 20] (time (dotimes [_ 10000] (empty? p1))))
(dotimes [i 20] (time (dotimes [_ 10000] (empty? t))))
(dotimes [i 20] (time (dotimes [_ 10000] (empty? t1))))


结果

||coll||before||after||result||
|p|0.72 ms|0.08 ms|为空时速度更快|
|p1|0.15 ms|0.13 ms|非空时略有更快|
|t|error|0.19 ms|不再出错|
|t1|error|0.20 ms|不再出错|

不确定是否应该调整文档字符串使其更通用,特别是 "same as (not (seq coll))",现在只对 Seqable 正确,但不适用于 Counted。我认为对于序列检查使用 (seq coll) 的建议仍然很好。

我对其他既是 Counted 又不是 sequences/Seqable 的类型进行了快速搜索,但没有找到很多,除了类似 ChunkBuffer 的内部事物。许多既是 Counted 也是 Sequenceable,因此会使用计数的路径(例如,所有持久集合和任何类型的 IndexedSeq)。

我认为另一个选择可能是将 {{empty?}} 完全切换到关于 (zero? (bounded-count 1 coll)),并完全依赖 count 的多态性。

*补丁:* clj-1872.patch

5 个答案

0 投票

评论者:alexmiller

可能类似于 CLJ-700。

0 投票

评论者:devn

正如在 CLJ-700 中提到的,这是一个不同的问题。

0 投票

评论者:devn

首先,原始描述中提到了 (empty? (transient ()))。根据https://clojure.org/reference/transients 中的文档,在列表上支持临时集合是没有好处的。

Java 集合的当前行为

`
(empty? (java.util.HashMap. {}))
=> true

(empty? (java.util.HashMap. {1 2}))
=> false

(seq (java.util.HashMap. {1 2}))
=> (#object[java.util.HashMap$Node 0x4335c9c3 "1=2"])

(seq (java.util.HashMap. {}))
=> nil
`

Java 数组也有同样的行为。

在 CLJS-2802 中,当前补丁的方法是通过对 empty? 中的问题使用 cond 进行处理,并显式检查它是否是 TransientCollection,如果是,使用原始描述中提到的作为 workaround 的 (zero? (count coll))

当前,临时集合没有实现 Iterable,而持久集合实现了。如果实现了 Iterable,我相信 RT.seqFrom 将会工作,并且通过外延,也适用于 empty?

0 投票

评论者:alexmiller

我认为临时集合不应该可迭代有几个很好的理由 - 迭代意味着缓存,缓存会影响性能,而使用临时集合的整个原因就是为了批量加载性能。这似乎是适得其反的。迭代器是状态的,而且我又怀疑可能为只是检查空值而添加它可能是一件坏事。

显式检查 counted? 集合的空值将覆盖所有临时集合以及任何其他未进行序列的计数集合。这可能在所有这些情况下加快速度,而不需要实现者在其他方面做出任何新的要求。

另一个选择是有一个 IEmptyable 接口和/或协议来指示显式空值检查支持。可能有些过度。

0 投票
参考:https://clojure.atlassian.net/browse/CLJ-1872(由 alex+import 报告)
...