两极化
两极化

Reputation: 143

How does clojure bind variable parameters?

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

Answers (3)

andy_fingerhut
andy_fingerhut

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

andy_fingerhut
andy_fingerhut

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

andy_fingerhut
andy_fingerhut

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

Related Questions