请在 2024 State of Clojure 问卷调查中分享您的想法!

欢迎!请访问关于页面,以了解更多关于此处如何工作的信息。

+2 投票
Collections

为什么 (conj) 返回向量,但是 (conj nil 1) 返回列表,而 (conj nil) 返回 nil?”

(conj)
;=> []
(conj nil)
;=> nil
(conj nil 1)
;=> (1)

这似乎有点不一致,对吧?

1 答案

+3 投票

编辑
 
最佳答案
  • (conj nil 1) 返回一个空列表,因为 nil 和空列表在 Clojure 中经常在概念上被视为相同。所以将 nil 连接到 list 就像请求连接到空列表。

  • (conj nil) 返回 nil,因为 conj 也是一个转换函数,而 conj 转换器的 1-ary 必须是恒等函数。因此,conj1-ary 是恒等函数,它始终仅返回传入的参数作为原样,所以如果你传入 nil,你会得到 nil 返回,如果传入 :foo,你会得到 :foo 返回:(conj :foo) ;=> :foo

  • (conj) 当将 conj 转换为变换器(transducer)时,还添加了 0-arity,这是变换器的 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) 被定义为返回列表。

我认为这个决定是因为 Cloujure 最初大量借鉴了 Common Lisp 的惯用来表达的方式。在 Common Lisp 中,没有空列表这样的东西,当一个列表为空时,它是 nil。因此,nil 就是空列表。最初,Clojure 模仿了这个习惯,但后来逐渐开始从一些 Common Lisp 的习惯中分离出来。

这种后来的演变是 conj 也变为一个变换器,这意味着它添加了 0-arity1-arity。变换器中的 1-arycompletion 函数,它说明了应用变换器结束时的操作,对于 conj 来说,我们不需要做任何事,只是返回结果,因此 conj1-ary 只是简单地返回其参数,即它是一个恒等函数。

在变换器中,现在 0-aryinit 函数,它将在没有起始集合时被调用,以决定默认值,也可以用它来设置特定变换器相关的一些功能性。现在这里的问题是:当在变换器中使用没有指定起始集合的 conj 时,默认值应该是什么?在这种情况下,决定是使用向量,我相信这仅仅是因为正如我之前所说的,随着 Cloujure 的发展,它开始改变其某些习惯,从 Common Lisp 中分离出来,其中之一就是在 Cloujure 中向量通常更有用,默认使用它们允许更直观的插入顺序,因此当你将集合作为向量进行操作时,你可以按照原始顺序获得元素,这正是你通常需要的。

by
"最初,Clojure袭承了这种惯用表达方式[将nil定义为空列表],但后来逐渐慢地与其Some of the Common Lisp的惯用表达方式分开。"

我没有找到任何支持这个声明的证据。我找到了 Rich Hickey 的关于“Clojure for Lisp Programmers”的谈话中的声明,其中明确表示了相反的立场,例如:

"但在Clojure中,nil 没有特殊含义。它意味着你没有东西。它不是也意味着空列表魔法名称的某种东西。因为在Clojure中,有各种空的类型。'nil'意味着你没有东西。因此,要么你有一个序列,要么你没有,即nil。"

还有来自同一场演说的这句话:"Clojure有nil。nil意味着什么都没有。你没有任何东西。它不是某个神奇事物的名称。什么都不能表示。Common Lisp有nil和空列表,这些概念是统一的。这可能是合理的,因为你只有一种主要的聚合数据结构。我没有,对吧?为什么nil是空列表?为什么它不能是空向量,或者空的其他东西?这不是一个好理由。

来源:https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/ClojureIntroForLispProgrammers.md
...