Reputation: 3231
I expanded the macro below to see how it worked and found myself a little confused.
(loop for i below 4 collect i)
expands to (I have cleaned it up a little for readability)
(block nil
(let ((i 0))
(declare (type (and number real) i))
(let* ((list-head (list nil))
(list-tail list-head))
(tagbody
sb-loop::next-loop
(when (>= i 4) (go sb-loop::end-loop))
(rplacd list-tail (setq list-tail (list i)))
(setq i (1+ i))
(print "-------") ;; added so I could see the lists grow
(print list-head)
(print list-tail)
(print "-------")
(go sb-loop::next-loop)
sb-loop::end-loop
(return-from nil (cdr list-head))))))
..and here is the output from running the above..
;; "-------"
;; (NIL 0)
;; (0)
;; "-------"
;; "-------"
;; (NIL 0 1)
;; (1)
;; "-------"
;; "-------"
;; (NIL 0 1 2)
;; (2)
;; "-------"
;; "-------"
;; (NIL 0 1 2 3)
;; (3)
;; "-------"
I just can't see where list-head is modified, I have to assume head and tail are eq
and thus modifying one is modifying the other but could someone please break down what is happening on the rplacd
line?
Upvotes: 7
Views: 290
Reputation: 14335
list-head
and list-tail
are initially the same (in the eq
sense). list-head
is a cons which cdr is the list being collected. list-tail
points to the last cons in the list (except initially, see below).
To add an element to the end of the list, replacd
modifies the cdr of list-tail
to add a new cons, and list-tail
is updated to point to the new cons.
When the loop terminates, the result is the cdr of list-head
.
Why this complicated business with an extra cons? Because the list appending algorithm becomes easier when list-tail
is always a pointer to the last cons of the list. But in the beginning, the empty list has no cons. So the trick is to make the list one cons longer.
Upvotes: 10