Reputation: 143
I am a newbie in Clojure.The problem originated when I once checked the source code of 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)))))
The source code of conj
shows that it uses recur to implement the function. This source code looks very simple.
What I'm confused about is the condition it uses when determining whether recursion needs to continue. It looks like it checks if the variable parameters is nil
, but if the variable parameters is nil
, it will soon be equivalent to the third "arity" of conj
?
I then tried to evaluate the following expression:
user=> (conj [] 1 (next []))
[1 nil]
user=>
It works normally and successfully adds nil
to the vector. I understand that clojure actually wraps nil
in a list and passes it to the function, but I don't understand why recur
can pass a real nil
in ? And why does clojure recognize and match the correct "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]
Upvotes: 2
Views: 355
Reputation: 1516
OK, my apologies for not realizing earlier that you have come across an aspect of Clojure behavior that I had never seen before. Today I learned something new about Clojure, and given 10 years with it, that surprised me.
This is mentioned in a couple of sentences of the official Clojure documentation for recur, here: https://clojure.org/reference/special_forms#recur
Here is a page of community-written examples and documentation (so not "official") that describes this behavior of recur with functions that have variadic arguments: https://clojuredocs.org/clojure.core/recur#example-55ff3cd4e4b08e404b6c1c7f
Upvotes: 1
Reputation: 1516
The part of the function declared with parameters [coll x & xs]
means "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".
You can see this with this simpler function that does not use recur at all:
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)
Upvotes: 0
Reputation: 1516
I would recommend making a copy of this function, changing its name to something else, and adding some calls to println
of the values of whatever interests you, e.g. x, xs, etc, and watch what gets printed in a Clojure REPL session when you call your function with different parameters that interest you.
The if xs
is true if the value of xs
is any value at all other than nil
or false
. The value (nil)
is a list of one element, and is considered true when used as an if
conditional expression.
Upvotes: 0