random-state.net

Nikodemus Siivola

<< next | top | previous >>

Why I Like CALL-WITH-* Style In Macros #
hacking, June 6th 2007

One, fairly common, style of writing macros is to expand WITH-* into CALL-WITH-*:

(defmacro with-foo ((foo) &body body)
  `(call-with-foo (lambda (,foo) ,@body)))

(defun call-with-foo (function)
  (let (foo)
    (unwind-protect
        (funcall function (setf foo (get-foo)))
      (when foo (release-foo foo)))))

There are several advantages to this, in comparison to expanding directly into the UNWIND-PROTECT:

  • In CALL-WITH-FOO style the implementation can be altered without needing to recompile all use-sites: simply recompiling the CALL-WITH-FOO is in most cases enough.
  • Better debuggability: if an error occurs in the body, CALL-WITH-FOO appears in the backtrace.
  • Less memory use and better localilty: instead of injecting UNWIND-PROTECT (or whatever) into all use-sites, only a lambda + function call needs to be genearated.
  • Better interface design: there are likely to be cases where users will prefer using CALL-WITH-FOO directly, if the already have a function that just needs the argument.

An additional benefit is that when using a coverage tool like the super-cool SB-COVER you are liable to get better coverage information using this style. (There may be coverage tools out there that don't benefit from this, but I'm not using them, so...)

One other point worth mentioning is the

((foo) &body body)

lambda-list: it is relatively future proof: if you some day realize you need a variant that eg. supports timeouts, you can change the lambda-list to

((foo &key timeout) &body body)

without altering all the use sites (they still need to be recompiled), which would be both tedious and error prone.