(conj nil 1)
returns an empty list
, because nil
and empty list
are often conceptually treated the same by Clojure. So conjoining on nil
is like asking to conjoin on empty list
.
(conj nil)
returns nil
because conj
is a transducer function as well, and the 1-ary
for the transducer of conj
has to be the identity function. Thus the 1-ary
of conj
is the identity function and will always just return the passed argument as-is, so if you pass nil
you get nil
back, if you pass in :foo
you get :foo
back: (conj :foo) ;=> :foo
(conj)
the 0-arity
was also added when conj
was made a transducer and is the init
arity for transducers. Thus it was probably deemed that when used with transduce, vector would be a better default, as most would expect that: (transduce (map inc) conj [1 2 3])
would return 2 3 4
and not 4 3 2
Initially conj
only had a 2-arity
: (conj nil 1)
or (conj [1 2] 3)
or (conj '() 1)
. This was in the time before transducers, and conj
was exclusively a collection function. From that perspective, the question was, what should (conj nil 1)
return? And the logical answer was, we should probably treat nil
as the empty list, so nil
should default to an empty list
and thus (conj nil 1) ;=> (1)
was made to return a list
.
I believe this decision was due to initially Clojure copying a lot of the Common Lisp idioms. In Common Lisp, there is no such thing as an empty list, when a list is empty it is nil
. Thus nil
is the empty list. Initially, Clojure copied this idiom, but later evolved to distance itself slowly from some of the Common Lisp idioms.
Such a later evolution is the evolution of conj
to also be a transducer, which meant that a 0-arity
and a 1-arity
were added to it. The 1-ary
in transducers is the completion
function, it says what to do at the end of applying a transducer, and in the case of conj
we don't need to do anything except return the result, thus the 1-ary
of conj
just returns its argument as is, aka it's an identity function.
Now the 0-ary
in transducers is the init
function, this will be called when there is no starting collection in order to decide what the default is, and it could be used to setup some other things related to the particular transducer. Now the question here was again: what should be the default when conj
is used in a transducer with no starting collection specified? In this case it was decided that would be vector
, and I believe this is simply because as Clojure evolved like I said before, it started to change some of its idioms away from Common Lisp, one of those being that in Clojure vectors tend to just be more useful, and defaulting to them allow for the more intuitive insertion order, so when you process a collection as a vector, you get your elements back in their original order which is just more often what you want.