< Previous | Next >
May 22, 2010 8:45 PM CDT by psilord in category Lisp

Stay to the Left

I've been learning lisp by writing a lot of it lately and I've run into the first time I needed a real defmacro to do some work for me. The project I'm working on uses a lot of DEFSTRUCTs and so sometimes I have to manhandle a pile of structures in one piece of code. I hate typing the accessors, so I use WITH-SLOTS a lot.

Usually, it looks like this (a contrived example that isn't far from the truth):

(with-slots (a b c) x
  (with-slots (d e f) y
    (with-slots (g h i) z
      (setf a d
            e h))))

After a while, I even hated typing all of that too and how it made my code inch towards the right. I'm an old hacker; I like 80 columns for my code! Being in the process of reading "On Lisp" by Paul Graham and "Let Over Lambda" by Doug Hoyte, I figured some miniscule portion of that awesome knowledge might have wedged itself between my ears as I felt it rushing past. I thought for a bit and came up with this:

;; You are free to use, modify, and redistribute this macro. Attribution to
;; me, Peter Keller (psilord@cs.wisc.edu), is appreciated, but not required.
;; There is no warranty with this code.

(defmacro multiple-with-slots (sbinds &body body)
  (if (null sbinds)
      `(progn ,@body)
      `(with-slots ,(car sbinds) ,(cadr sbinds)
         (multiple-with-slots ,(cddr sbinds) ,@body))))

Yup, that's right, it is a recursive macro! It is probably overengineered and a real lisp professional would chuckle a bit and mention something in the ANSI spec I managed to overlook which does exactly what I wanted already--not to mention the N ways it is probably wrong. However, it is the first nontrivial macro I've written and so it will hold a special place in my heart.

You use it like this:

* ;; In a lisp REPL

* (defstruct xx a b c)
* (defstruct yy d e f)
* (setf x (make-xx :a 1 :b 2 :c 3))
* (setf y (make-yy :d 4 :e 5 :f 6))

* (multiple-with-slots
      ((a b c) x
       (d e f) y)
    (rotatef a d))

* x

#S(XX :A 4 :B 2 :C 3)
* y

#S(YY :D 1 :E 5 :F 6)

What I very much like about this is that the slot names are generalized variables which allows manipulation by SETF, ROTATEF and all manner of things like that automatically work. No different than WITH-SLOTS, but I didn't lose the behavior either, and that was important to me.

I may rewrite it a bit later to take advantage of grouping functions to rip apart the sbinds list, but for now, it is quite useful to me.

Note: It sorta sucks that everyone these days has to explicitly state some licensing aspect about their code they publish. I guess the trials and tribulations of software lawsuits have taken their toll.


End of Line.

< Previous | Next >