Fixing compiler macro annoyances in Allegro CL
Allegro CL is a nice Lisp implementation to use, but I recently discovered one caveat that was nearly a show-stopper for my work: compiler macros are not expanded for setf functions. This is allowed by the standard, but is frustrating nevertheless. As Willem Broekema pointed out, the lack of expansion of setf compiler macros results in some weirdness when setf is expanded and the place is a compound form that corresponds to a function with a compiler macro.
I used the past tense above because I managed to come up with a way around this for my purposes. Namely, I use *macroexpand-hook* to inhibit or expand the compiler macro functions when in a compilation environment. This is possible because ACL has environments support, which means it is possible to determine if a symbol names a compiler macro and whether it is applicable.
To inhibit the compiler macros, any compiler macro function that could cause problems is temporarily disabled when the setf form is expanded. This amounts to saving the compiler macro function and expanding the setf form in an unwind-protect.
Expanding the compiler macro function for the setf function is more involved. To properly expand the compiler macro, you would have to get the expansion of the setf form and look for the form that makes the call to the setf function. Doing this properly requires a code walker. I didn’t want to bother with that since the situations I need to deal with are specific and I’m not defining any custom setf expanders.
I took the following approach. If the setf form matches (setf (foo ...) ...) (*) and there is a compiler macro function for (setf foo) that can be applied, then disable compiler macros as above and get the expansion using get-setf-expansion. If the storing form matches (funcall #'(setf foo) ...) then expand the compiler macro for (setf foo) and use the form it returns in place of the storing form.
This solution is not suitable for all situations since it doesn’t expand forms in the correct order. Specifically, it doesn’t expand any forms that evaluate to the arguments passed to (setf foo) before getting its compiler macro expansion (these are returned by get-setf-expansion). I also didn’t bother with psetf, but that’s only because I didn’t need it.
I thought of two other ways that you could go about expanding setf compiler macros, but I haven’t thought the approaches through in great detail.
- If
setfcalls are always of the form(funcall #'(setf foo) ...)then you could put a compiler macro function onfuncalland call the appropriate compiler macro. I played with this a little, but ACL defines a compiler macro forfuncallalready and I’m not sure what it does. - You could define a
setfexpander to return a different form when in a compilation environment. I’m not convinced this is markedly different than the above approach and it would probably involve more work or be less aesthetically pleasing.
Code for this is available here with a colourized html version as well. The interface needs work, but it is suitable for my purposes.
(*) Note that (setf p1 v1 p2 v2 ...) is transformed into the equivalent (progn (setf p1 v1) (setf p2 v2) ...) before looking for this pattern.

2 comments:
Maybe I'm missing something, but wouldn't it have been much
easier to simply shadow CL:SETF, and write your own version
which inspects the setter return value of GET-SETF-EXPANSION?
Plus this would work on all implementations.
Shadowing CL:SETF would eliminate the need to check for SETF forms, but it doesn't make detecting and disabling compiler macros any easier or harder. The result of GET-SETF-EXPANSION is the "mangled" form that results in the oddities that Willem pointed out.
So you still have to disable compiler macros before getting the SETF expansion, which means you need environments support.
Post a Comment