As a note to Sylwester's answer, here is a version of your stack-push
which at first sight looks correct but which in fact has an ugly problem (thanks to jkiiski for pointing this out!), followed by a simpler version which still has the problem, and finally a variant of the simpler version which does not.
Here is the initial version. This agrees with your signature (it takes either a single argument which cannot be a list, or a list of arguments, and decides what to do based on what it sees).
(defmacro stack-push (stack element/s)
;; buggy, see below!
(let ((en (make-symbol "ELEMENT/S")))
`(let ((,en ,element/s))
(typecase ,en
(list
(setf ,stack (append (reverse ,en)
,stack)))
(t
(setf ,stack (cons ,en ,stack)))))))
However I would be more tempted to write it with an &rest
argument, as follows. This version is simpler because it always does one thing. It is still buggy though.
(defmacro stack-push* (stack &rest elements)
;; still buggy
`(setf ,stack (append (reverse (list ,@elements)) ,stack)))
This version could be used as
(let ((a '()))
(stack-push* a 1 2 3)
(assert (equal a '(3 2 1))))
for instance. And it seems like it works.
Multiple evaluation
But it doesn't work, because it can multiply-evaluate things which should not be multiply-evaluated. The easiest way (I found) to see this is to look at what the macro expansion is.
I have a little utility function to do this, called macropp
: this just calls macroexpand-1
as many times as you ask for, pretty printing the result. To see the problem you need to expand twice: first to expand stack-push*
and then to see what happens to the resulting seetf
. The second expansion is implementation-dependent, but you can see the problem. This sample is from Clozure CL which has a particularly simple expansion:
? (macropp '(stack-push* (foo (a)) 1) 2)
-- (stack-push* (foo (a)) 1)
-> (setf (foo (a)) (append (reverse (list 1)) (foo (a))))
-> (let ((#:g86139 (a)))
(funcall #'(setf foo) (append (reverse (list 1)) (foo (a))) #:g86139))
And you can see the problem: setf
doesn't know anything about foo
so it's just calling #'(setf foo)
. It carefully makes sure that the subforms are evaluated in the right order, but it just evaluates the second subform in the obvious way, with the result that (a)
gets evaluated twice, which is wrong: if it has side-effects then they will happen twice.
So the fix for this is to use define-modify-macro
whose job is to solve this problem. To do this, you define a function which makes the stack, and then use define-modify-macro
to make the macro:
(defun stackify (s &rest elements)
(append (reverse elements) s))
(define-modify-macro stack-push* (s &rest elements)
stackify)
And now
? (macropp '(stack-push* (foo (a)) 1) 2)
-- (stack-push* (foo (a)) 1)
-> (let* ((#:g86170 (a)) (#:g86169 (stackify (foo #:g86170) 1)))
(funcall #'(setf foo) #:g86169 #:g86170))
-> (let* ((#:g86170 (a)) (#:g86169 (stackify (foo #:g86170) 1)))
(funcall #'(setf foo) #:g86169 #:g86170))
And you can see that now (a)
is only evaluated once (and also that you only now need a single level of macroexpansion).
Thanks again to jkiiski for pointing out the bugs.
macropp
For completeness, here is the function I use to pretty-print macro expansions. This is just a hack.
(defun macropp (form &optional (n 1))
(let ((*print-pretty* t))
(loop repeat n
for first = t then nil
for current = (macroexpand-1 form) then (macroexpand-1 current)
when first do (format t "~&-- ~S~%" form)
do (format t "~&-> ~S~%" current)))
(values))