Here's an implementation that gives read and write access to the values in the alist.
To start, define a macro using symbol-macrolet, where a bound symbol in the body expands to code that accesses that symbol's value in the alist:
(defmacro with-alist% (alist-entries instance-form &body body)
`(symbol-macrolet
,(loop for (alist-binding alist-entry) in alist-entries
collect `(,alist-binding (cdr (assoc ',alist-entry ,instance-form))))
,@body))
And test it:
(defparameter *al* (list (cons 'foo 5) (cons 'bar 'a) (cons 'baz "z")))
*AL*
CL-USER>
(print *al*)
((FOO . 5) (BAR . A) (BAZ . "z"))
((FOO . 5) (BAR . A) (BAZ . "z"))
CL-USER>
(with-alist% ((foo foo) (bar bar)) *al*
(format t "foo=~a, bar=~a~%" foo bar)
(setf foo 1)
(setf bar "a") ...)
foo=5, bar=A
foo=1, bar=a
NIL
CL-USER>
(print *al*)
((FOO . 1) (BAR . "a") (BAZ . "z"))
((FOO . 1) (BAR . "a") (BAZ . "z"))
CL-USER>
Note that not only can you use the bound symbol for printing, you can also setf the symbol, which maps to changing the value associated with that symbol in the alist. This is what using symbol-macrolet (instead of let) provides. Also, if you read the source code for with-slots, at least for CCL, symbol-macrolet is how that macro is implemented. Again, I'm working towards a similar syntactic feel of with-slots.
with-alist% works well enough when you have an alist of mixed keywords and symbols, but I would think that most often you'll have just symbols, which means that there will be a lot of code duplication when using that function, for example:
(with-alist% ((foo foo) (bar bar)) *al*
Note how each alist entry is listed twice. What if you want the bound symbol and alist entry to always be the same? It would be nice if you could do this:
(with-alist (foo bar) *al*
And that macro:
(defmacro with-alist (alist-entries instance-form &body body)
`(with-alist% ,(mapcar (lambda (alist-entry)
(if (consp alist-entry)
`,alist-entry
`,(list alist-entry alist-entry)))
alist-entries)
,instance-form
,@body))
Usage:
(print *al*)
CL-USER>
((FOO . 1) (BAR . "a") (BAZ . "z"))
((FOO . 1) (BAR . "a") (BAZ . "z"))
CL-USER>
(with-alist (foo (bar% bar)) *al*
(format t "foo=~a, bar=~a~%" foo bar%)
(setf foo 5)
(setf bar% 'a) ...)
foo=1, bar=a
foo=5, bar=A
NIL
CL-USER>
(print *al*)
((FOO . 5) (BAR . A) (BAZ . "z"))
((FOO . 5) (BAR . A) (BAZ . "z"))
CL-USER>
REPL
I agree with Rainer's comment about only evaling instance-form once, when you only want read access. However, if you want read/write access, then instance-form will need to be expanded every time that a bound symbol is found in body. Otherwise, you'll be writing to an let binding of instance-form; not instance-form; and therefore instance-form won't be changed.
A good read for common-lisp macro programming is definitely On Lisp, and also Let Over Lambda. Getting through LOL is IME a serious time investment, but well worth it if you want to improve your common lisp macro programming skills.
let-withmacro. :-) – Chris Jester-Young Apr 13 '12 at 4:00