Clojure 2024 状态调查 中分享您的想法!

欢迎!请在 关于 页面了解如何工作的更多信息。

+2
Collections

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

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

看起来有点不一致,对吧?

1 答案

+3

编辑
 
最佳回答
  • (conj nil 1) 返回一个空的 list,因为 Clojure 习惯上将 nil 和空列表看作是同样的概念。所以,在 nil 上连接就像是请求在空列表上连接。

  • (conj nil) 返回 nil,因为 conj 也是一个 transducer 函数,而 conj 的单参数必须是一个恒等函数。因此,conj 的单参数是恒等函数,它将始终返回传递的参数原样,因此如果您传递 nil,您将得到 nil 回传;如果您传递 :foo,您将得到 :foo 回传: (conj :foo) ;=> :foo

  • 当将conj变为转换器时,也增加了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 应该默认为一个空 list,因此 (conj nil 1) ;=> (1) 被设置为返回一个 list

我认为这个决定可能是由于 Clojure 最初大量复制了 Common Lisp 的风格。在 Common Lisp 中,没有空序列这一说,当一个序列为空时,就是 nil。因此 nil 就是空序列。最初,Clojure 复制了这个风格,但后来逐渐与一些 Common Lisp 风格有所不同。

后来这种发展为将 conj 变为转换器,这意味着为它增加了 0-arity1-arity。转换器中的 1-aritycompletion 函数,它说明在应用转换器结束时应该做什么,在 conj 的情况下,我们不需要做任何事情,除了返回结果,所以 conj1-arity 只返回其参数,即它是一个恒等函数。

现在,转换器中的 0-arityinit 函数,当没有初始收集时会被调用,以决定默认值,并可以用来设置与特定转换器相关的某些事项。这里的问题是:在将 conj 用于没有指定起始集合的转换器时,应该有什么默认值?在这种情况下,决定应该是 vector,我相信这只是一个简单的事实,因为 Clojure 如我以前所说,开始改变自己的风格,其中之一是从 Common Lisp 风格转变为自己的风格,这其中包括在 Clojure 中向量通常更有用,默认使用它们允许更直观的插入顺序,当处理一个序列作为向量时,你将按其原始顺序获取元素,这往往是更常需要的结果。

by
"最初,Clojure 复制了这种(将 nil 视为空序列)风格,但后来逐渐与一些 Common Lisp 风格有所不同。"

我找不到任何支持这个声明的事实。我可以找到 Rich Hickey 在“Clojure for Lisp Programmers”演讲中的声明,明确表示相反,例如。

"但 nil 在 Clojure 中什么也不代表。它意味着你没有东西。它并不意味着某个魔术名称也意味着空序列。因为 Clojure 中有空的向量,还有所有种类的空东西。“nil”意味着你没有东西。所以或者你有它,一个序列,或者你没有,nil。"

同样,这段来自同一个演讲的引用:“Clojure 有 nil。nil 代表空值。你没有东西。它不是某种魔法的名称。它什么都不是。Common Lisp 有 nil 和空列表,这些概念相互统一。这可能是有意义的,因为你真的只有一个主要的聚合数据结构。我没有,对吧?为什么 nil 应该是空列表?为什么它不应该是空向量或空的其他东西?没有充分的理由。”

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