当输入是向量时,为什么clojure.core/rest
输出列表?
这会产生意想不到的效果:
(conj [1 2 3] 4)
; => [1 2 3 4]
(conj (rest [1 2 3]) 4)
; => (4 2 3)
我知道"它从产生这种效果的文档调用 seq 的参数"。我不明白为什么这是预期的效果。作为一个天真的用户,我希望(rest [1 2 3])
表现得像(subvec [1 2 3] 1)
。我知道我可以将subvec
用于我的用例。为了学习,我想了解rest
的基本原理,以及需要输出列表的用例(即使输入是向量(。
rest
的输出不是一个列表,而是一个seq
,这是一个更低级别的抽象。来自rest
的官方文档:
返回第一个项之后的可能为空的序列。在其上调用seq论点。
混淆源于两者都是在parens之间打印的事实,但如果你仔细观察,它们是不同的:
user=> (list? (rest [1 2 3]))
false
user=> (seq? (rest [1 2 3]))
true
它与list
有何不同seq
?seq
是用一个需要实现first
、rest
和cons
的接口实现的,但细节取决于集合实现。例如,向量使用自己的实现:
user=> (class (rest [1 2 3]))
clojure.lang.PersistentVector$ChunkedSeq
user=> (class (rest '(1 2 3)))
clojure.lang.PersistentList
列表是一个实现,它至少扩展了一个基本的 Seq 接口,并建立在上面。例如,clojure.lang.PersistentList
实现了需要常量时间版本的count
的Counted
接口。
有关 Seqs 和列表之间差异的详细说明,请查看以下链接:
- 序列和列表之间的差异
- https://clojure.org/reference/sequences
你为返回向量的向量rest
提供了一个很好的案例。麻烦的是,rest
是序列的基本操作之一,而向量不是序列:
=> (seq? [1 2 3 4])
false
但是,如果rest
可以接受像向量这样的seq
的东西,你可以说它应该能够返回这样的东西。
它返回什么?
=> (type (rest [1 2 3 4]))
clojure.lang.PersistentVector$ChunkedSeq
这给人一种被seq
电话包裹的subvec
的假象。
我知道"它在参数上调用seq">
这是正确的。Seqs
是用一个接口(ISeq
(实现的,需要实现first
、rest
和cons
。
rest
采用任何Seq'able
(实现ISequable
的任何集合(。使用它的原因是效率和简单性。
不同的集合的工作方式,获得first
和rest
的最有效方式是不同的。
这就是为什么当您将一个集合转换为seq
时,它将在rest
和其他集合上实现最有效的实现。
我希望这很清楚
我同意这种行为是出乎意料和违反直觉的。 作为解决方法,我在 Tupelo 库中创建了append
和prepend
函数。
从文档中,我们看到示例:
Clojure 具有 cons、conj 和 concat 函数,但不清楚如何使用它们向向量或列表的开头添加新值:
; Add to the end
> (concat [1 2] 3) ;=> IllegalArgumentException
> (cons [1 2] 3) ;=> IllegalArgumentException
> (conj [1 2] 3) ;=> [1 2 3]
> (conj [1 2] 3 4) ;=> [1 2 3 4]
> (conj '(1 2) 3) ;=> (3 1 2) ; oops
> (conj '(1 2) 3 4) ;=> (4 3 1 2) ; oops
; Add to the beginning
> (conj 1 [2 3] ) ;=> ClassCastException
> (concat 1 [2 3] ) ;=> IllegalArgumentException
> (cons 1 [2 3] ) ;=> (1 2 3)
> (cons 1 2 [3 4] ) ;=> ArityException
> (cons 1 '(2 3) ) ;=> (1 2 3)
> (cons 1 2 '(3 4) ) ;=> ArityException
你知道当你传递 nil 而不是序列时 conj 会做什么吗?它会以静默方式将其替换为空列表:(conj nil 5)
⇒(5)
如果您不知道默认行为,这可能会导致您以相反的顺序累积项目:
(-> nil
(conj 1)
(conj 2)
(conj 3))
;=> (3 2 1)
这些故障令人恼火且效率低下,错误消息不会清楚地表明出了什么问题。相反,请使用简单的prepend
和append
函数分别向序列的开头或结尾添加新元素:
(append [1 2] 3 ) ;=> [1 2 3 ]
(append [1 2] 3 4) ;=> [1 2 3 4]
(prepend 3 [2 1]) ;=> [ 3 2 1]
(prepend 4 3 [2 1]) ;=> [4 3 2 1]
prepend
和append
始终返回向量结果。