请在2024 年 Clojure 调查中分享您的想法!

欢迎!请查看关于页面获取更多有关如何使用本站的信息。

+2
集合

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

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

这似乎有些不一致?

1 答案

+3

编辑
 
最佳答案
  • (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 中抽取出来,其中之一就是向量通常更实用,默认使用它们可以允许更有直观的插入顺序,因此当你按向量处理集合时,你会得到你元素的原顺序返回,这通常是你想要的。

by
"最初,Clojure 复制了这种习惯(nil 作为空列表),但后来逐渐逐渐地与一些 Common Lisp 习惯了断开联系。"

我找不到任何支持这种陈述的证据。我可以找到 Rich Hickey 在“Clojure for Lisp Programmers”演讲中的陈述,明确表示相反的观点,例如。

"但是 nil 在 Clojure 中没有意义。它意味着你没有什么。它并不意味着一些魔法名称,同时也意味着空列表。因为有空向量,还有空的其他各种东西。“nil”意味着你没有东西。所以要么你有它,一个序列,要么你什么都没有,nil。"。

同时,这也是同一场谈话中的引言:“Clojure 有 nil。nil 表示 nothing。你没有任何东西。它不是一个神奇东西的名字。它什么也不代表。Common Lisp 也有 nil 和空列表,这些概念是统一的。这可能是有意义的,因为你实际上只有一个主要聚合数据结构。我没有,对吗?为什么 nil 应该是空列表?为什么它不能是空向量,或其他什么空的东西?这没有任何好理由。”

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