I'm was playing around with the do macro, and I decided to write a function to reverse a list:
(defun my-reverse (my-list)
(do ((alist my-list (cdr alist))
(acc nil (cons (car alist) acc)))
((null alist) acc)
(format t "current: ~a~%" (car alist))))
--output:--
CL-USER> (my-reverse '(10 20 30))
current: 10
current: 20
current: 30
(30 20 10)
All good so far. But I discovered that changing do to do* produced a different result:
(defun my-reverse (my-list)
(do* ((alist my-list (cdr alist))
(acc nil (cons (car alist) acc)))
((null alist) acc)
(format t "current: ~a~%" (car alist))))
--output:--
(CL-USER> (my-reverse '(10 20 30))
current: 10
current: 20
current: 30
(NIL 30 20)
What is the explanation for that?
Also, because (car alist) appears twice in the code, I thought I would try to initialize a variable and set it equal to (car alist) in order to simplify the code:
(defun my-reverse (my-list)
(do* ((alist my-list (cdr alist))
(x (car alist))
(acc nil (cons x acc)))
((null alist) acc)
(format t "current: ~a~%" x)))
--output:--
CL-USER> (my-reverse '(10 20 30))
current: 10
current: 10
current: 10
(10 10 10)
Okay, x needs a stepper:
(defun my-reverse (my-list)
(do* ((alist my-list (cdr alist))
(x (car alist) (car alist))
(acc nil (cons x acc)))
((null alist) acc)
(format t "current: ~a~%" x)))
--output:--
CL-USER> (my-reverse '(10 20 30))
current: 10
current: 20
current: 30
(NIL 30 20)
That didn't work either.
Next, I tried doing the accumulation in the body:
(defun my-reverse (my-list)
(do* ((alist my-list (cdr alist))
(x (car alist) (car alist))
(acc nil))
((null alist) acc)
((setf acc (cons x acc))
(format "current: ~a~%")
))
But, I got an "illegal function call" error for:
(setf acc (cons x acc))
Is the body allowed to change the loop variables?
The relation between
doanddo*is similar to that betweenletandlet*: parallel assignment vs sequential.Quoting from the hyperspec:
So in
(cons (car alist) acc), the value ofalistis the previous one, not what it's set to for this iteration.Initially,
alistis'(10 20 30), andaccisnil. In the second iteration,alistis'(20 30)andaccis'(10), and so on.Whereas with
do*,so it's seeing the current version of
alist, which, in the second iteration is'(20 30)(And so 20 is appended toacc) and in the last iteration isnil, and(car nil)isnil.