Creating a ‘returning’ macro in Lisp
Aug 25th, 2007 by phil
I have been reading Practical Common Lisp and learning quite a lot in the process. However, I began to see a pattern emerging in the code that deals with objects that is also very common in Ruby. The pattern emerges where you have a class and you want to perform some initialization beyond what the normal initializers provide you. I will refer to the following class in this example:
;;; Lisp User class
(defclass user ()
(login
password
first-name
last-name))
The recurring pattern involves code that looks like this:
(let ((u make-instance user))
(with-slots (first-name last-name) u
(setf first-name "Foo")
(setf last-name "Bar"))
u)
In other words, we want to create an instance of a class within a let form, manipulate that instance somehow and then return it from the form. This example is trivial and could have been accomplished using initializers, but I think it still illustrates the pattern.
Rails has a function that makes this pattern a little cleaner, at least to my eyes. The function is called returning, is part of the ActiveSupport library and is defined as:
def returning(value)
yield(value)
value
end
This is very simple and it allows you to write the Ruby equivalent of our user initialization code like this:
def create_user
returning User.new do |u|
u.first_name = "Foo"
u.last_name = "Bar"
end
end
Translating this into Lisp should be a fun and reasonable simple exercise. First, lets try a naive transliteration of the returning function from Ruby to Lisp. That might leave us with the following code:
(defun returning (value fn)
(let ((var value))
(funcall fn var)
var))
This is a literal and faithful translation of the code from Ruby to Lisp and it allows us to write our user initialization code like so:
(returning (make-instance 'user)
(lambda (u)
(with-slots (first-name last-name) u
(setf first-name "Foo")
(setf last-name "Bar"))))
This code has a couple of problems. First, it is more verbose than I would like. The final term is no longer hanging off the bottom but we have added a lambda form that wasn’t there before. Second, this really isn’t very idiomatic Lisp code (at least as far as I can tell with my still limited knowledge of Lisp).
The problem is that Lisp’s lambda forms are not really the same thing as Ruby’s blocks. In fact, the closest thing Ruby has to a Lisp lambda is a Proc object which you can create, oddly enough, with the lambda keyword. The returning function in Ruby is taking advantage of blocks to implement the Template Method pattern. While you can use lambda in Lisp to create a template method, there is a more elegant solution: macros.
So, lets take a stab at creating a ‘returning’ macro in Lisp. We will need to take the name of the variable to use and its initial value as parameters to the macro. We will also need the body of the form where all of the work gets done. This gives us the following signature:
(defmacro returning (var value &body body) ...)
The &body works like &rest and collects the rest of the forms passed to the macro into the body parameter. The let form way back at the beginning of this post provides a template for what we want the body of the macro to look like. If we take the original let form, quote it and substitute var, value and body in the appropriate places we get this:
(defmacro returning (var value &body body)
`(let ((,var ,value))
,@body
,var))
Since body is a list we have to splice it into the current form using the @ operator. This does almost what we want. We can use it to rewrite our original initialization code using returning in place of the original let:
(returning u (make-instance 'user)
(with-slots (first-name last-name) u
(setf first-name "Foo")
(setf last-name "Bar")))
There is a tiny problem with this code as well. The variable name and initialization form are hanging out there at the end of the line and there is no indication that they are related. We would really like to put a pair of parenthesis around both the u and the make-instance form so that it is clear they are related. Fortunately, we can “destructure” lists that are passed to macros as parameters. If we change the signature of the returning macro to include parenthesis around var and value we can write the code like this:
(returning (u (make-instance 'user))
(with-slots (first-name last-name) u
(setf first-name "Foo")
(setf last-name "Bar")))
Finally, what if we want to leave off the initialization form? Perhaps we want to initialize the variable ‘u’ in the body. This is easily accomplished by adding &optional to the macro parameter list before the value parameter:
(defmacro returning ((var &optional value) &body body)
`(let ((,var ,value))
,@body
,var))
This allows us to initialize u in the body of the form:
(returning (u)
(setf u (make-instance 'user))
(with-slots (first-name last-name) u
(setf first-name "Foo")
(setf last-name "Bar")))
It can be helpful to see how a macro expands. The macroexpand-1 function does exactly what we want. Here are the expansions of our returning macro both with and without the initialization form (the output has been cleaned up a bit):
CL-USER> (macroexpand-1 '(returning (u (make-instance 'user))
(with-slots (first-name last-name) u
(setf first-name "Foo")
(setf last-name "Bar"))))
(LET ((U (MAKE-INSTANCE 'USER)))
(WITH-SLOTS (FIRST-NAME LAST-NAME) U
(SETF FIRST-NAME "Foo")
(SETF LAST-NAME "Bar"))
U)
T
CL-USER> (macroexpand-1 '(returning (u)
(setf u (make-instance 'user))
(with-slots (first-name last-name) u
(setf first-name "Foo")
(setf last-name "Bar"))))
(LET ((U NIL))
(SETF U (MAKE-INSTANCE 'USER))
(WITH-SLOTS (FIRST-NAME LAST-NAME) U
(SETF FIRST-NAME "Foo")
(SETF LAST-NAME "Bar"))
U)
T




