The perils of multiple-value-prog1


In learning Common Lisp I come across some pretty esoteric stuff (Lisp is like that). I tend to learn a language from specification-like documents. One thing this does is give me a view of the language that is probably different from how a typical programmer in that language sees it. It’s difficult for me to get a pragmatic view of the language. In Lisp for example, do people use PROG (as opposed to LET, PROGN, and TAGBODY)? What is the value of (VALUES)? How often do people put a reader macro on !? And, the subject of this post, does anyone ever use MULTIPLE-VALUE-PROG1?

Well, I turned to a co-worker who gets paid to program in Lisp. He said, “Yes, it’s quite often used in macros”; he turned to his current project and found 5 uses of it, all in macros. It turns out that 2 of those uses were buggy (I never did get paid for improving the quality of that project). I was tempted to humourously advertise MULTIPLE-VALUE-PROG1 with a strapline: “Causes experts to write buggy code 40% of the time”. A typical use is when you have a macro that takes a body (list of forms), where the expansion of the macro executes the body, and then does some more stuff (cleanup perhaps), but wants to pretend that really it’s just the the body that being executed, so the resulting form will yield the same values that the body alone does.

You can’t use just a PROGN like this:

(defmacro foo (... &body body)
  (progn ,@body (more-stuff ...)))

because PROGN won’t return the values from the body, it’ll return values from more-stuff. Combining PROG1 and PROGN gives the almost right:

(defmacro foo (... &body body)
  (prog1 (progn ,@body) (more-stuff ...)))

but it goes wrong when body returns other than exactly 1 value; it’s not multiple value transparent in other words. That’s why you need MULTIPLE-VALUE-PROG1:

(defmacro foo (... &body body)
  (multiple-value-prog1 (progn ,@body) (more-stuff ...)))

The bug is when you forget the inner PROGN:

(defmacro foo (... &body body)
  (multiple-values-prog1 ,@body (more-stuff ...)))

This returns multiple values, but unfortunately it returns all the values from the first form of body, not its last form. In the, probably all too common, case where body has only one form, you won’t spot the difference and this bug might go unnoticed for some while.

The amusing icing on the cake is that you can use Google’s codesearch facility to find bugs like this automatically: search for “multiple-value-prog1 ,@”. When I searched I got three results. The first result is a correct implementation of a MULTIPLE-VALUE-PROG2 macro, the other 2 are bugs. The code for the first bug is:

(defmacro waiting-for-token-event ((contact &optional (token-data 0)) &body body)
  `(multiple-value-prog1 ,@body
     (await-token-event ,contact ,token-data)))

Now you know what to look for you should be able to see how this follows the typical use pattern outlined above, and is also not correct.

Searching for multiple-value-prog1 alone shows that most of the time this appears in Lisp code it is either to describe how to format MULTIPLE-VALUE-PROG1 forms in a pretty printer or code indenter or similar, or it appears in a compiler test suite. There aren’t very many actual uses of it. Well, that’s the impression I get when viewing the world through Google’s codesearch spectacles.

2 Responses to “The perils of multiple-value-prog1”

  1. It’s worse than that. If you’re using MULTIPLE-VALUE-PROG1 because you need to do cleanups, this is a bug by itself! You ought to be using UNWIND-PROTECT instead.

    I’ve never seen sensible use of MULTIPLE-VALUE-PROG1 myself, and I can’t think of a use case, either.

  2. drj11 Says:

    You’re right, MULTIPLE-VALUE-PROG1 is an attractive nuisance for the cleanup case. It tempts you but it’s wrong.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: