The Special Interpretation of nil
cons
is a special form (i.e. it is not a function, but a compiler built-in). cons
knows that a nil
means "no more data is coming".
(cons 7 nil) => (7)
(cons 7 '()) => (7)
(cons 7 []) => [7]
So, if either when-let
or when
fails, nil
is returned and we have something like (cons 7 nil)
. Therefore, the lazy sequence is terminated (the nil
is discarded), and at this point it is equivalent to a plain list.
Returning nil
Your question surprised me! I hadn't thought it would work, but here is the code:
(defn odd->nil [it]
(if (odd? it)
nil
it))
(defn down-from
"Count down from N to 1"
[n]
(lazy-seq
(when (pos? n)
(cons (odd->nil n) (down-from (dec n))))))
(down-from 5) => (nil 4 nil 2 nil)
So we see there is a big difference between nil
being the first or 2nd argument to cons
. If nil
is the first arg, it is added to the beginning of the list as normal. If nil
is the second arg, it is (silently) converted into an empty list, and the result is a 1-element list:
(cons nil [99]) => (nil 99) ; works like normal
(cons 99 nil) => (99) ; creates a 1-elem list
(cons nil nil) => (nil) ; for completeness
P.S.
Note there is a slight contradiction with seq
, since we have:
(seq nil) => nil
P.P.S rest
vs next
I never use next
, since I don't like silent conversions to nil
:
(next [1]) => nil
(next []) => nil
(next nil) => nil
I prefer to use rest
, since it will give me an empty list as I expect:
(rest [1]) => ()
(rest []) => ()
(rest nil) => ()
I can then write a test like so:
(let [remaining (rest some-seq) ]
(when-not (empty remaining) ; says what it means
....more processing.... ))
I don't like assumptions about silent conversions:
(when (next some-seq) ; silently converts [] => nil
....more processing.... ) ; & relies on nil <=> false
One Last Thing
You may be interested in a small refinement called lazy-cons
described here. I think it is a bit simpler than the original lazy-seq
.
(defn lazy-countdown [n]
(when (<= 0 n)
(lazy-cons n (lazy-countdown (dec n)))))
(deftest t-all
(is= (lazy-countdown 5) [5 4 3 2 1 0] )
(is= (lazy-countdown 1) [1 0] )
(is= (lazy-countdown 0) [0] )
(is= (lazy-countdown -1) nil ))
It also has a cousin the emulates Python-style generator functions.