Recently, our attention has been on Lisp features related to multiple values. As mentioned previously, the values accessor allows the programmer to return zero or more values from a form, unlike C++ where a function can return at most one value.
Certain Lisp constructs encourage the use of progn, a feature that executes several forms in series and returns the values of the last form in the group. Examples include if, unwind-protect, and any other feature that evalutes exactly one form in some context. In that case, progn allows the programmer to collect several forms together and build a single form out of them for syntactic purposes.
One way that newcomers to Lisp can get tripped up is to think that prog1 or prog2 are exactly the same as progn, just returning the values from different forms in the series. In fact, prog1 and prog2 return only the primary value of the corresponding form, they do not return all of the values.
If the programmer has a need to retrieve the multiple values from the first form in prog1, he or she should use multiple-value-prog1. As suggested by the name, this special operator executes the forms inside it in order, and returns all values of the first form.
So, we’ve got progn, which returns all values. We have prog1, which returns only the primary value, so we have multiple-value-prog1 to fill that gap. We also have prog2 which, like prog1, does not return all values, but there isn’t a multiple-value-prog2.
Well, we can always write our own. Here’s an example of a macro that achieves the effect of multiple-value-prog2:
(defmacro multiple-value-prog2 (&body body) `(progn ,(first body) (multiple-value-prog1 ,@(rest body))))
Note that, with this macro, you get an error at macro expansion time if body does not contain at least two forms, but prog2 has the same behaviour, so I don’t consider that a bug in this macro.
Now, just for completeness, a transcript of some operations involving multiple values, also including the effect of that macro above:
CL-USER> (progn (values 1 1) (values 2 2) (values 3 3)) 3 3 CL-USER> (prog1 (values 1 1) (values 2 2) (values 3 3)) 1 CL-USER> (prog2 (values 1 1) (values 2 2) (values 3 3)) 2 CL-USER> (multiple-value-prog1 (values 1 1) (values 2 2) (values 3 3)) 1 1 CL-USER> (multiple-value-prog2 (values 1 1) (values 2 2) (values 3 3)) 2 2