LISP - Macros
Lisp - An Introduction to Macros
Section titled “Lisp - An Introduction to Macros”Macros are one of the most powerful and defining features of Common Lisp. They allow you to extend the language’s syntax itself. A common saying is that macros are code that writes code.
Unlike a function, which operates on values at runtime, a macro is a transformation function that takes Lisp code (s-expressions) as input and produces new Lisp code as output at compile time. This new code is then compiled and executed in place of the original macro call.
Defining a Macro with defmacro
Section titled “Defining a Macro with defmacro”You define a macro using the defmacro special operator. Its syntax is similar to defun:
(defmacro macro-name (parameter-list) "Optional documentation string." body-of-macro)The body of the macro must return a Lisp form (a piece of code) that will replace the macro call.
Example 1: A Simple Logging Macro
Section titled “Example 1: A Simple Logging Macro”Let’s create a macro with-logging that prints a message before and after executing a block of code. This is something a function cannot do without changing how the code is called.
To build code inside a macro, we use the backquote ( ) syntax. It acts like a template. We use a comma (,) to evaluate and insert a value into this template, and comma-at (,@) to splice a list of values.
main.lisp
Section titled “main.lisp”(defmacro with-logging ((&key (stream '*standard-output*)) &body body) "Executes BODY, printing a log message before and after." `(progn (format ,stream "--- Entering logged section ---") ,@body (format ,stream "--- Exiting logged section ---")))
;; Now, let's use our new syntax(with-logging () (print "Doing some work...") (let ((x 10) (y 20)) (format t "The sum is ~a~%" (+ x y))))Output
Section titled “Output”When you execute this, Lisp first expands the macro into the progn form and then runs the resulting code:
--- Entering logged section ---"Doing some work..."The sum is 30--- Exiting logged section ---How it Works
Section titled “How it Works”The &body keyword in the parameter list collects all subsequent forms into a single list called body. The macro body then constructs a progn block (which evaluates expressions sequentially). The ,@body splices the list of forms from the body variable directly into the progn block.
Example 2: Creating a while Loop
Section titled “Example 2: Creating a while Loop”Common Lisp has loop, but let’s define our own while loop to demonstrate how macros can create new control flow structures.
main.lisp
Section titled “main.lisp”(defmacro while (test &body body) "Executes BODY as long as TEST evaluates to true." `(loop while ,test do (progn ,@body)))
;; Use our new `while` loop(let ((counter 3)) (while (> counter 0) (format t "Counter is ~a~%" counter) (decf counter)))Output
Section titled “Output”This code expands into a standard loop macro call and produces the expected result:
Counter is 3Counter is 2Counter is 1When to Use Macros
Section titled “When to Use Macros”- To create new syntax or control flow structures: As seen with
with-loggingandwhile. - To control evaluation: When you need to prevent arguments from being evaluated, or control how many times they are evaluated (e.g., short-circuiting logical operators like
andandor). - To reduce boilerplate code at compile time: Writing code that generates repetitive, but slightly different, definitions.
- Guideline: If you can solve a problem with a regular function, use a function. Reach for a macro only when a function is insufficient.