Whether you can do something with higher order functions or even
simpler constructs isn't a great argument against having macros.
Recently I needed to do something for every string of length N or
fewer. So I wrote a function that returned a closure that enumerated
all strings until exhausting the string space. But I had to loop over
the string space a few times in the program, and it got really
cumbersome to write things like this:
(flet ((next-string (make-string-enumerator <num>)))
(loop for str = (next-string)
until (null str)
do <body>))
Once I'd written that twice, I realized that I was eventually going
to make mistakes writing or rewriting expressions like that, so I
wrote a macro that expanded into that expression. Once I had the
macro, I could write the following instead of the above:
(dostrings (str <num>)
<body>)
Now, maybe you're thinking "That's only a two-line savings in any
place where you'd need to do what you're doing", but to me the win is
that the macro lets me write only the important things (the <body>,
the maximum string length <num>), and the macroexpander takes care of
the tedious and error-prone code for me. This isn't just saving
typing: I don't have to think up a variable name like
next-string whenever I need to loop over all strings, and
so I never have to track down bugs introduced by changing variable
names when refactoring. If I don't use it outside the macro, I don't
even need to remember the purpose or interface for
make-string-enumerator, either.
Yeah, I could have written something analogous to the <body> form
as a function of the string returned by the enumerator, and written
the original loop like this:
(let ((next-string (make-string-enumerator <num>)))
(labels ((looper (body enumerator)))
(let ((str (funcall enumerator)))
(when str
(funcall body str)
(looper body enumerator)))))
But I'm not sure that's easier to understand than either the
original loop form or the dostrings macro, and anyhow it's still long
enough that I think I'd want something like my dostrings macro to
generate it if I had to write something like that more than once.
Functions (including higher order functions and closures) are one
way of factoring code for maintainability; macros are another. They
complement each other.
|