The syntax (++ a)
is a useless alias for (incf a)
. But suppose you want the semantics of post-increment: retrieve the old value. In Common Lisp, this is done with prog1
, as in: (prog1 i (incf i))
. Common Lisp doesn't suffer from unreliable or ambiguous evaluation orders. The preceding expression means that i
is evaluated, and the value is stashed somewhere, then (incf i)
is evaluated, and then the stashed value is returned.
Making a completely bullet-proof pincf
(post-incf
) is not entirely trivial. (incf i)
has the nice property that i
is evaluated only once. We would like (pincf i)
to also have that property. And so the simple macro falls short:
(defmacro pincf (place &optional (increment 1))
`(prog1 ,place (incf ,place ,increment))
To do this right we have to resort to Lisp's "assignment place analyzer" called get-setf-expansion
to obtain materials that allow our macro to compile the access properly:
(defmacro pincf (place-expression &optional (increment 1) &environment env)
(multiple-value-bind (temp-syms val-forms
store-vars store-form access-form)
(get-setf-expansion place-expression env)
(when (cdr store-vars)
(error "pincf: sorry, cannot increment multiple-value place. extend me!"))
`(multiple-value-bind (,@temp-syms) (values ,@val-forms)
(let ((,(car store-vars) ,access-form))
(prog1 ,(car store-vars)
(incf ,(car store-vars) ,increment)
,store-form)))))
A few tests with CLISP. (Note: expansions relying on materials from get-setf-expansion
may contain implementation-specific code. This doesn't mean our macro isn't portable!)
8]> (macroexpand `(pincf simple))
(LET* ((#:VALUES-12672 (MULTIPLE-VALUE-LIST (VALUES))))
(LET ((#:NEW-12671 SIMPLE))
(PROG1 #:NEW-12671 (INCF #:NEW-12671 1) (SETQ SIMPLE #:NEW-12671)))) ;
T
[9]> (macroexpand `(pincf (fifth list)))
(LET*
((#:VALUES-12675 (MULTIPLE-VALUE-LIST (VALUES LIST)))
(#:G12673 (POP #:VALUES-12675)))
(LET ((#:G12674 (FIFTH #:G12673)))
(PROG1 #:G12674 (INCF #:G12674 1)
(SYSTEM::%RPLACA (CDDDDR #:G12673) #:G12674)))) ;
T
[10]> (macroexpand `(pincf (aref a 42)))
(LET*
((#:VALUES-12679 (MULTIPLE-VALUE-LIST (VALUES A 42)))
(#:G12676 (POP #:VALUES-12679)) (#:G12677 (POP #:VALUES-12679)))
(LET ((#:G12678 (AREF #:G12676 #:G12677)))
(PROG1 #:G12678 (INCF #:G12678 1)
(SYSTEM::STORE #:G12676 #:G12677 #:G12678)))) ;
T
Now here is a key test case. Here, the place contains a side effect: (aref a (incf i))
. This must be evaluated exactly once!
[11]> (macroexpand `(pincf (aref a (incf i))))
(LET*
((#:VALUES-12683 (MULTIPLE-VALUE-LIST (VALUES A (INCF I))))
(#:G12680 (POP #:VALUES-12683)) (#:G12681 (POP #:VALUES-12683)))
(LET ((#:G12682 (AREF #:G12680 #:G12681)))
(PROG1 #:G12682 (INCF #:G12682 1)
(SYSTEM::STORE #:G12680 #:G12681 #:G12682)))) ;
T
So what happens first is that A
and (INCF I)
are evaluated, and become the temporary variables #:G12680
and #:G12681
. The array is accessed and the value is captured in #:G12682
. Then we have our PROG1
which retains that value for return. The value is incremented, and stored back into the array location via CLISP's system::store
function. Note that this store call uses the temporary variables, not the original expressions A
and I
. (INCF I)
appears only once.