So, in a series of posts, I talked about Lisp macros and their power.
I started out saying that there were features of Lisp that I missed when programming in other languages. Ultimately, the languages are for the programmer, not the computer. If your language has support for the functions you need, and the compiler is a good one, the computer is perfectly fine running your code in whatever language you choose. Once those essentials have been covered, the choice of language comes down to familiarity and ease of use.
In this series of articles, I described how to set up Lisp macros to create a new looping structure in the language, for a particular use case I had in my work. I was writing looping code over and over again, in a format like this:
(labels ((circular-inc (dlist iter) (let ((rv (dl-list:iter-next dlist iter))) (or rv (dl-list:iter-front dlist))))) (do* ((iter0 (dl-list:iter-front dlist) (circular-inc dlist iter0)) (iter1 (circular-inc dlist iter0) (circular-inc dlist iter0))) (nil) (let ((contents0 (dl-list:iter-contents dlist iter0)) (contents1 (dl-list:iter-contents dlist iter1))) (perform-some-function-on-content-pair contents0 contents1) (when (eq iter0 (dl-list:iter-back dlist)) (return)))))
With the help of the macros, this can be rewritten:
(dl-list:2-value-loop (dlist contents0 contents1 :circular t) (perform-some-function-on-contents contents0 contents1))
This not only saves typing, and reduces the opportunity for typos introducing bugs, but it makes the block of code immediately readable to the user. The code is much more self-documenting, as the maintainer doesn’t have to scan through a dozen lines of boilerplate code to see what this particular loop does. The programmer doesn’t have to look out for variable-name collisions in the iterator variables, (s)he can rely on the macro to avoid that danger. To a person reviewing or editing the code, it is sufficient to know that this loop iterators over all pairs of values in a circular list, without needing to know exactly how that is actually implemented. It hides the boring, repetitive parts out of sight, much the way a compiler or assembler hides registers and memory addresses from the C programmer.
In C++ you can use classes and methods to reduce some of the on-screen clutter, but building a new flow-control structure and using it this concisely is difficult, if even possible. When I converted my code to C++, I wound up with specialized iterators, pair_iterators, triplet_iterators, and when you start putting in const-types and reverse iterators you wind up with hundreds of lines of code that is boring to write. Even then, the C++ version is less concise when used in the code.
So, that’s one of the things I miss when I’m not programming in Lisp, the ability to build new flow-control structures that aid greatly in keeping the code concise and readable.