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?}} 中添加一个分支以支持计数的 coll。临时集合实现了 Counted,因此通过此分支获得支持。其他计数的 coll 更快。序列分支将继续为序列工作。

性能测试


(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|不再报错|

不确定是否应该修改文档字符串使其更通用,特别是对于 "等同于 (not (seq coll))",现在这只对 Seqable 成立,而对 Counted 不成立。我认为使用 (seq coll) 进行序列检查的建议仍然适用。

我在其他具有计数的类型上进行了快速检查,但没有找到太多东西,除了像 ChunkBuffer 这样的内部项目。许多既是也是,因此会使用计数的路径(例如所有持久的 coll 和任何类型的 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上的文档,在列表上支持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?中通过显式检查是否为TransientCollection来围绕问题进行cond,并像原始描述中提到的那样使用(zero? (count coll))作为 workaround。

当前,transient集合没有实现Iterable,如持久的集合那样。如果实现 Iterable,我认为RT.seqFrom将工作,进而empty?

0

评论者:alexmiller

我认为transient集合不支持Seqable有很好的理由——seqs意味着缓存,缓存会影响性能,而使用transients的整个原因就是为了批量负载性能。这似乎是适得其反的。迭代器是状态的,而且,我怀疑仅为了检查empty?而添加它可能是不好的。

对计数?集合的显式空检查将覆盖所有transient集合以及任何其他计数但没有创建seq的东西。这可能对这所有情况更快,并且不需要在实现中要求任何新事物。

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

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