(conj nil 1)
返回一个空列表,因为在 Clojure 中,nil 和空列表通常在概念上被视为相同。因此,在 nil 上进行连接就像请求在空列表上进行连接。
(conj nil)
返回nil,因为 conj 也是一个转换函数,并且 conj 转换器的 1-ary 必须是恒等函数。因此,conj 的 1-ary 是恒等函数,并且将始终返回传递的参数作为原样,所以如果你传递nil,你会得到nil;如果你传入 :foo
,你会得到 :foo
: (conj :foo) ;=> :foo
当将 conj 转换为转换器(transducer)时,也添加了 0 参数版本,这是转换器的 init 参数。因此,很可能认为在使用 transduce 时,向量会是更好的默认选择,因为大多数人会期望 (transduce (map inc) conj [1 2 3])
返回 2 3 4
,而不是 4 3 2
最初,conj 只有 2-arity
:例如 (conj nil 1)
或 (conj [1 2] 3)
或 (conj '() 1)
。这在前转换器时代,conj 主要是作为一个集合函数使用的。从这个角度来看,问题是,(conj nil 1)
应该返回什么?合理的答案是我们可能将 nil 视为空列表,所以 nil 应该默认为空列表,因此 (conj nil 1) ;=> (1)
被改为返回一个列表。
我认为这个决定是由于 Clojure 最初大量复制了 Common Lisp 的习惯。在 Common Lisp 中,没有空列表这种东西,当列表为空时它是 nil
。因此,nil 是空列表。最初,Clojure 复制了这个习惯,但后来逐渐逐渐地与一些 Common Lisp 习惯了断开联系。
这样的后续演化为 conj 也是一个转换器,这意味着它增加了一个 0 参数和一个 1 参数。转换器中的 1 参数是 completion
函数,它说明在应用转换器结束时该做什么,对于 conj 来说,我们不需要做任何事,只需返回结果即可,因此 conj 的 1 参数只是直接返回其参数,也就是说是恒等函数。
现在,转换器中的 0 参数是 init
函数,当没有起始集合时会被调用,以决定默认值,并且可以用来设置与特定转换器相关的一些其他事情。现在这里的问题又来了:当 conj 在没有指定起始集合的转换器中使用时,默认值应该是什么?在这种情况下,决定是使用 vector
,我相信这仅仅是因为如我之前所述,Clojure 在发展过程中开始改变其习惯,其中之一就是将 Clojure 的习惯从 Common Lisp 中抽取出来,其中之一就是向量通常更实用,默认使用它们可以允许更有直观的插入顺序,因此当你按向量处理集合时,你会得到你元素的原顺序返回,这通常是你想要的。