我是Clojure的新手,问题源于我曾经检查过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
添加到向量中。我知道 clojure 实际上将nil
包装在一个列表中并将其传递给函数,但我不明白为什么recur
可以传递一个真正的nil
?为什么 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]
好的,我很抱歉没有意识到你之前遇到了我以前从未见过的Clojure行为的一个方面。 今天,我学到了一些关于Clojure的新知识,并用了10年的时间,这让我感到惊讶。
这在官方 Clojure 文档的几句话中提到过:https://clojure.org/reference/special_forms#recur
这里有一页社区编写的示例和文档(所以不是"官方的"(,描述了这种使用具有可变参数的函数的递归行为:https://clojuredocs.org/clojure.core/recur#example-55ff3cd4e4b08e404b6c1c7f
我建议复制这个函数,将其名称更改为其他名称,并添加一些调用来println
您感兴趣的任何值,例如 x、xs 等,并观察当您使用您感兴趣的不同参数调用函数时在 Clojure REPL 会话中打印的内容。
如果xs
的值是除nil
或false
以外的任何值,则if xs
为 true。 值(nil)
是一个元素的列表,当用作if
条件表达式时,该值被视为 true。
函数中用参数[coll x & xs]
声明的部分意味着"将第一个参数的值绑定到 coll,将第二个参数的值绑定到 x,然后如果没有更多参数,则将 xs 与值 nil 绑定,否则将 xs 与作为剩余参数列表的值绑定"。
你可以通过这个根本不使用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)