in reply to Re^2: Overcoming addiction to Lisp
in thread Overcoming addiction to Lisp

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.

Replies are listed 'Best First'.
Re^4: Overcoming addiction to Lisp
by Anonymous Monk on Jun 16, 2005 at 20:01 UTC
    Maybe I missed it, but why would dostrings need to be a macro, instead of a function? And if you don't like making up non-sense names, just use perl's default name, $_. Besides, it really sounds like you should be using the builtin list traversal operators like grep, map, and foreach instead of rolling your own.
      ...it really sounds like you should be using the builtin list traversal operators...

      The code is not traversing a list. It is creating an iterator, and executing a block of code for each item returned by the iterator. You might argue that a foreach{...} is an iterator, but that would require generating the entire list first, which may not be appropriate here. In perl it would be more like:

      my $nxt_str = make_strings($n); while (my $str = $nxt_str->()) { #..do stuff with $str }

        Or else you could use a tied array, and that way you'll get to use all the standard array goodies, without reinventing the wheel.