With OpenGL, certain things have to happen between a glBegin and a glEnd, and certaing things are not allowed between a glBegin/End pair. One of them being a second glBegin. The poor OpenGL programmer now has one more thing to worry about, and tedious boilerplate to write like a robot (in this case just a one-liner glEnd() but try not to lose the big picture principle, OK Sparky? This is a deliberately simple example). Is there any way to alleviate this drudgery?
Well, quite a few statements will be squeezed of between glBegin and glEnd, cuz OpenGL is no joy to code. We can write a function called do-between-begin-end and pass it a first class function (unless your BDFL does not see the need), but then (in CL) we end up with:
(do-between-begin-end
(lambda ()
...your dozen lines of code here...
))
Not the end of the world, but remember, I am just warming up. These macro posts will go on forever. Anyway, with a macro we lose the lambda speed bump:
(with-gl-begun ()
...your dozen lines here...)
What's with the ()? Well, truth is glBegin takes a parameter that says what we are beginning (line strip? triangles? quads?) so I kinda lied, the contrast should be...
(do-between-begin-end gl_quads
(lambda ()
...your dozen lines of code here...))
and:
(with-gl-begun (gl_quads)
...your dozen lines here...)
So, yeah, I can write do-between-gl-begin-end and never worry about begin and end again, but I am stuck with a little line noise there.
If we look at a classic example of how Lisp macros hide boilerplate, we see this more egregious after/before pair:
(with-open-file (f "xxx")
(print "line 1 of xxx:")
(print (read-line f)))
Versus what I have to write without with-open-file:
(let ((f (open "xxx"))
(success nil))
(unwind-protect
(multiple-value-prog1
(progn
(print "line 1 of xxx follows:")
(print (read-line f))
(close f))
(setf success t))
(unless success
(close f :abort t))))
The odd thing is that Pythonistas reject macros while fervently embracing tidy code -- and Python code does look very clean -- but all we are doing with macros (so far) is making code neater. Well, no, we are also releaving the programmer of drudgery and making the code more reliable by automating the insertion of necessary boilerplate such as error checking.
Let's turn now to a dead simple hiding of boilerplate, Paul Graham's AIF swiped from the inestimable On Lisp:
(defmacro aif (test-form then-form &optional else-form)
‘(let ((it ,test-form))
(if it ,then-form ,else-form)))
The alternative is:
(let ((temp (big-long-fn a b c)))
(if temp
(handle-it temp x y)
(do-something-else a x))
By the way, if you do not like Graham's anaphoric thing (using "it" as the implicit temporary variable), use my BIF which takes a mnemonic for the intermediate result:
(defmacro bif (bindvar boundform yup &optional nope)
`(let ((,bindvar ,boundform))
(if ,bindvar ,yup ,nope)))
And then a more realistic use:
(bif sol (math-problem-solution prb mx)
(display-solution sol)
(tell-user "No can do!"))
In this just my first offering on macros I have discussed only the advantages of automatically generating boilerplate. There is much more to come, but I would like to close by reiterating my befuddlement that this even needs explaining, and in the context of the C preprocessor precedent mentioned above:
I just dug into the old C version of my Algebra software for just one out of hundreds of macros, one which iterates over the children of a node (a custom tree struct in my application) in something akin to the C "for" statement:
#define fork(PAR,TMP_KID,NXT_KID) \
for ( NXT_KID = (TMP_KID = firstk( PAR))? nexts(TMP_KID):NIL \
; TMP_KID \
; NXT_KID = (TMP_KID = NXT_KID)? nexts(TMP_KID):NIL)
The crucial thing to note is that this macro is meant to allow children to be unlinked from the parent during the traversal, so it is necessary to capture the next child after the current child before processing the current child, because parents only see their first and last child as I had things arranged.
So now I could write code like this (instead of something like the macro definition above):
fork( usg, tusg, nusg) {
kidDetach( tusg); ;; losing the "next child" link
xInsKid( tpar, pusg, tusg);
}
Next time we will do something even jazzier.