【发布时间】:2020-03-06 20:54:16
【问题描述】:
在 Clojure 中,我应该更喜欢哪种方式来测试一个对象是否是一个空列表?请注意,我只想测试这个,而不是作为一个序列是空的。如果它是一个“懒惰的实体”(LazySeq、Iterate、...)我不希望它得到realized?。
下面我给出了x的一些可能的测试。
;0
(= clojure.lang.PersistentList$EmptyList (class x))
;1
(and (list? x) (empty? x))
;2
(and (list? x) (zero? (count x)))
;3
(identical? () x)
Test 0 的级别有点低,依赖于“实现细节”。我的第一个版本是(instance? clojure.lang.PersistentList$EmptyList x),它给出了IllegalAccessError。为什么呢?这样的测试不应该是可能的吗?
测试 1 和 2 更高级别且更通用,因为 list? 检查是否有实现 IPersistentList。我猜他们的效率也略低。请注意,两个子测试的顺序很重要,因为我们依赖于短路。
测试 3 假设每个空列表都是同一个对象。我所做的测试证实了这个假设,但它保证成立吗?即使是这样,依靠这个事实是一个好习惯吗?
这一切看似微不足道,但我有点困惑,没有为如此简单的任务找到一个完全直接的解决方案(甚至是内置函数)。
更新
也许我没有很好地提出这个问题。回想起来,我意识到我想要测试的是某个东西是否是一个非惰性空序列。我的用例最关键的要求是,如果它是惰性序列,它不会被实现,即不会强制执行 thunk。
使用“列表”一词有点令人困惑。毕竟什么是列表?如果它是像PersistentList 这样具体的东西,那么它是非懒惰的。如果它是像 IPersistentList 这样抽象的东西(这是 list? 测试的,可能是正确的答案),那么不完全保证非懒惰。碰巧 Clojure 当前的惰性序列类型没有实现这个接口。
所以首先我需要一种方法来测试某个东西是否是惰性序列。我现在能想到的最好的解决方案是使用IPending 来测试一般的懒惰:
(def lazy? (partial instance? clojure.lang.IPending))
虽然有一些惰性序列类型(例如,像 Range 和 LongRange 这样的分块序列)没有实现 IPending,但期望惰性序列通常实现它似乎是合理的。 LazySeq 这样做了,这在我的特定用例中才是真正重要的。
现在,依靠短路来防止 empty? 实现(并防止给它一个不可接受的参数),我们有:
(defn empty-eager-seq? [x] (and (not (lazy? x)) (seq? x) (empty? x)))
或者,如果我们知道我们正在处理像我这样的序列,我们可以使用限制较少的:
(defn empty-eager? [x] (and (not (lazy? x)) (empty? x)))
当然,我们可以为更通用的类型编写安全测试,例如:
(defn empty-eager-coll? [x] (and (not (lazy? x)) (coll? x) (empty? x)))
(defn empty-eager-seqable? [x] (and (not (lazy? x)) (seqable? x) (empty? x)))
话虽如此,推荐的测试 1 也适用于我的情况,这要归功于短路和 LazySeq 没有实现 IPersistentList 的事实。鉴于这一点以及问题的表述不是最理想的,我会接受 Lee 的简洁回答,并感谢 Alan Thompson 抽出时间和我们进行了有益的小型讨论,并投了赞成票。
【问题讨论】:
标签: clojure is-empty empty-list