Been messing around with macros recently and thought I’d give an introduction to the power of macros and the fun you can have with them. Lisp is expressed as lists, which means that you can write programs that write programs. One way to do this is with ‘eval’ : (eval ‘(+ 1 2 3)). This has limitation since a) the expressions are evaluated with no lexical context (defining a variable with a ‘let’ and then calling eval on some expressions, those expressions can’t use that variable) b) it is slow.
That is where macros come into play. Macros are basically a rule for translation. The compiler performs a translation, called macro-expansion. This means the code generated is actually part of your program, thus much faster. An example from Paul Graham’s book “ANSI Common Lisp” is adding a while loop to Lisp:
> (let ((x 10)
(while (< x 10)
(princ x)
(incf x)))
0123456789
NIL
The macro to implement this would be:
(defmacro while (test &rest body)
`(do ()
((not ,test))
,@body))
This is a very simple example, but gets the point across. The while macro takes a test argument and then any number of expressions to loop over while this ‘test’ is true. The ‘&rest body’ is a list of the list expressions, in the example above it would contain, ( (princ x) (incf x) ), lists in Lisp are represented with parens, obviously. The backquote ` turns evalution off, a comma turns it back on and a comma-at splices the list and evaluates, thus the list in ‘body’ would become two distinct lists and be evaluated one after the other. This allows the macro to be expanded into an expression without being evaluated yet, except when there is a comma which in this case would replace ‘,test’ with ‘(< x 10)’, without the comma ‘test’ would not be replaced and running this code would not work since Lisp would have no idea what ‘test’ is when the macro is expanded (I hope that is understandable, hard to explain it without more examples, just remember that the arguments to defmacro only live in defmacro not where the macro is used).You can do : “(pprint (marcoexpand-1 ‘(…while loop code…)))”, to see what Lisp actually runs after the macro expansion.
I always stupidly tried to use match on a string when I was learning Ocaml. So, I thought as a Lisp learning experience I’d implement it. The code I wanted to be able to use was:
(match str_1
str_2 (princ “first match worked”)
str_3 (princ “second match worked”)
str_4 (princ “third match worked”))
Turned out that this does not need a macro at all, haha. I ended up writing this function:
(defun match (str &rest tests)
(labels ((f_match (args)
(if (equal str (car args))
(apply #'(lambda () (car (cdr args))) ())
(if (null (cddr tests))
()
(f_match (cddr args))))))
(f_match tests)))
This post may have started off as being about macros, but I guess its more about the power of Lisp syntax. I can send a list of Lisp expressions as an argument and then run them. Not sure if (apply ….) is the best way to do it in this situation. It was just the easiest way I knew how to do it. This function is pretty simple. It loops through all the elements of ‘tests’ checking the first element against str and if it matches evaluating the second element. If it does not match it calls f_match recusively with the tail of the list starting at the third element, via the function (cddr tests).
Hope this made sense. I tried to keep it short and probably just made it more confusing by doing so. I’ll have more on macros later, maybe once I come up with a problem that actually needs a macro.