当输入是向量时,为什么 clojure.core/rest 会输出一个列表?



当输入是向量时,为什么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有何不同seqseq是用一个需要实现firstrestcons的接口实现的,但细节取决于集合实现。例如,向量使用自己的实现:

user=> (class (rest [1 2 3]))
clojure.lang.PersistentVector$ChunkedSeq
user=> (class (rest '(1 2 3)))
clojure.lang.PersistentList

列表是一个实现,它至少扩展了一个基本的 Seq 接口,并建立在上面。例如,clojure.lang.PersistentList实现了需要常量时间版本的countCounted接口。

有关 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(实现的,需要实现firstrestcons

rest采用任何Seq'able(实现ISequable的任何集合(。使用它的原因是效率和简单性。

不同的集合的工作方式,获得firstrest的最有效方式是不同的。

这就是为什么当您将一个集合转换为seq时,它将在rest和其他集合上实现最有效的实现。

我希望这很清楚

我同意这种行为是出乎意料和违反直觉的。 作为解决方法,我在 Tupelo 库中创建了appendprepend函数。

从文档中,我们看到示例:


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)

这些故障令人恼火且效率低下,错误消息不会清楚地表明出了什么问题。相反,请使用简单的prependappend函数分别向序列的开头或结尾添加新元素:

(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]

prependappend始终返回向量结果。

最新更新