clojure如何绑定可变参数?

How does clojure bind variable parameters?

我是Clojure.The的新手 问题出在我查看conj的源代码时:

    (def conj 
      (fn ^:static conj
        ([] [])
        ([coll] coll)
        ([coll x] (clojure.lang.RT/conj coll x));4
        ([coll x & xs] ;1
         (if xs ;2
           (recur (clojure.lang.RT/conj coll x) (first xs) (next xs)) ;3
           (clojure.lang.RT/conj coll x)))))

conj的源码显示,它使用recur来实现功能。这个源代码看起来很简单。 我感到困惑的是它在确定递归是否需要继续时使用的条件。貌似检查可变参数是不是nil,但是如果可变参数是nil,很快就相当于conj的第三个"arity"? 然后我尝试评估以下表达式:

user=> (conj [] 1 (next []))
[1 nil]
user=>

运行正常,成功将nil添加到vector中。我知道 clojure 实际上将 nil 包装在一个列表中并将其传递给函数,但我不明白为什么 recur 可以传递一个真正的 nil in ?为什么clojure会识别并匹配正确的"arity"?


user=> (def my_conj
   (fn [coll x & xs]
     (println "xs is" xs)
     (if xs
       (recur (clojure.lang.RT/conj coll x) (first xs) (next xs))
       (clojure.lang.RT/conj coll x))))
#'user/my_conj
user=> (my_conj [] 1 (next []))
xs is (nil)
xs is nil
[1 nil]

我建议复制此函数,将其名称更改为其他名称,并添加一些对 println 您感兴趣的值的调用,例如x、xs 等,并观察当您使用您感兴趣的不同参数调用函数时在 Clojure REPL 会话中打印的内容。

如果 xs 的值是除 nilfalse 之外的任何值,则 if xs 为真。值 (nil) 是一个元素的列表,当用作 if 条件表达式时被认为是真的。

函数中声明参数[coll x & xs]的部分表示"bind the value of the first parameter to coll, bind the value of the second parameter to x, then if there are no more parameters, bind xs with the value nil, otherwise bind xs with the value that is the list of the remaining parameters".

你可以通过这个根本不使用 recur 的更简单的函数看到这一点:

user=> (defn my-fn [a & xs]
         (println "a=" a " xs=" xs))

user=> (my-fn 1)
a= 1  xs= nil

user=> (my-fn 1 2)
a= 1  xs= (2)

好的,我很抱歉没有早点意识到您遇到了我以前从未见过的 Clojure 行为的一个方面。今天我学到了一些关于 Clojure 的新东西,并且用了 10 年,这让我很惊讶。

这在官方 Clojure 文档的几句话中提到了 recur,此处:https://clojure.org/reference/special_forms#recur

这是一页社区编写的示例和文档(因此不是 "official"),它描述了具有可变参数的函数的 recur 行为:https://clojuredocs.org/clojure.core/recur#example-55ff3cd4e4b08e404b6c1c7f