Common Lisp Macros By Example Tutorial - Lisp journey (2024)

I have recently edited and somewhat expanded the macros page on theCommon Lisp Cookbook. I find it may more legible and reader friendly,so I reproduce it below (however, I cut two parts so than you get the essential).

You’d better read it on the Cookbook: https://lispcookbook.github.io/cl-cookbook/macros.html

The word macro is used generally in computer science to mean a syntactic extension to a programming language. (Note: The name comes from the word “macro-instruction,” which was a useful feature of many second-generation assembly languages. A macro-instruction looked like a single instruction, but expanded into a sequence of actual instructions. The basic idea has since been used many times, notably in the C preprocessor. The name “macro” is perhaps not ideal, since it connotes nothing relevant to what it names, but we’re stuck with it.) Although many languages have a macro facility, none of them are as powerful as Lisp’s. The basic mechanism of Lisp macros is simple, but has subtle complexities, so learning your way around it takes a bit of practice.

A macro is an ordinary piece of Lisp code that operates on another piece of putative Lisp code, translating it into (a version closer to) executable Lisp. That may sound a bit complicated, so let’s give a simple example. Suppose you want a version of setq that sets two variables to the same value. So if you write

(setq2 x y (+ z 3))

when z=8 then both x and y are set to 11. (I can’t think of any use for this, but it’s just an example.)

It should be obvious that we can’t define setq2 as a function. If x=50 and y=-5, this function would receive the values 50, -5, and 11; it would have no knowledge of what variables were supposed to be set. What we really want to say is, When you (the Lisp system) see:

(setq2 v1 v2 e)

then treat it as equivalent to:

(progn (setq v1 e) (setq v2 e))

Actually, this isn’t quite right, but it will do for now. A macro allows us to do precisely this, by specifying a program for transforming the input pattern (setq2 v1 v2 e) into the output pattern (progn ...).

Quote

Here’s how we could define the setq2 macro:

(defmacro setq2 (v1 v2 e) (list 'progn (list 'setq v1 e) (list 'setq v2 e)))

It takes as parameters two variables and one expression.

Then it returns a piece of code. In Lisp, because code is representedas lists, we can simply return a list that represents code.

We also use the quote: each quoted symbol evaluates to itself, akait is returned as is:

  • (quote foo bar baz) returns (foo bar baz)
  • the quote character, ', is a shortcut for quote, a special operator (not a function nor a macro, but one of a few special operators forming the core of Lisp).
  • so, 'foo evaluates to foo.

So, our macro retourns the following bits:

  • the symbol progn,
  • a second list, that contains
    • the symbol setq
    • the variable v1: note that the variable is not evaluated inside the macro!
    • the expression e: it is not evaluated either!
  • a second list, with v2.

We can use it like this:

(defparameter v1 1)(defparameter v2 2)(setq2 v1 v2 3);; 3

We can check, v1 and v2 were set to 3.

Macroexpand

We must start writing a macro when we know what code we want togenerate. Once we’ve begun writing one, it becomes very useful tocheck effectively what code does the macro generate. The function forthat is macroexpand. It is a function, and we give it some code, asa list (so, we quote the code snippet we give it):

(macroexpand '(setq2 v1 v2 3));; (PROGN (SETQ V1 3) (SETQ V2 3));; T

Yay, our macro expands to the code we wanted!

More interestingly:

(macroexpand '(setq2 v1 v2 (+ z 3)));; (PROGN (SETQ V1 (+ z 3)) (SETQ V2 (+ z 3)));; T

We can confirm that our expression e, here (+ z 3), was notevaluated. We will see how to control the evaluation of arguments withthe comma: ,.

Note: with Slime, you can call macroexpand by putting the cursor atthe left of the parenthesis of the s-expr to expand and call the functionM-xslime-macroexpand-[1,all], or C-c M-m:

[|](setq2 v1 v2 3);^ cursor; C-c M-m; =>; (PROGN (SETQ V1 3) (SETQ V2 3))

Macros VS functions

Our macro is very close to the following function definition:

(defun setq2-function (v1 v2 e) (list 'progn (list 'setq v1 e) (list 'setq v2 e)))

If we evaluated (setq2-function 'x 'y '(+ z 3)) (note that eachargument is quoted, so it isn’t evaluated when we call thefunction), we would get

(progn (setq x (+ z 3)) (setq y (+ z 3)))

This is a perfectly ordinary Lisp computation, whose sole point of interest is that its output is a piece of executable Lisp code. What defmacro does is create this function implicitly and make sure that whenever an expression of the form (setq2 x y (+ z 3)) is seen, setq2-function is called with the pieces of the form as arguments, namely x, y, and (+ z 3). The resulting piece of code then replaces the call to setq2, and execution resumes as if the new piece of code had occurred in the first place. The macro form is said to expand into the new piece of code.

Evaluation context

This is all there is to it, except, of course, for the myriad subtle consequences. The main consequence is that run time for the setq2 macro is compile time for its context. That is, suppose the Lisp system is compiling a function, and midway through it finds the expression (setq2 x y (+ z 3)). The job of the compiler is, of course, to translate source code into something executable, such as machine language or perhaps byte code. Hence it doesn’t execute the source code, but operates on it in various mysterious ways. However, once the compiler sees the setq2 expression, it must suddenly switch to executing the body of the setq2 macro. As I said, this is an ordinary piece of Lisp code, which can in principle do anything any other piece of Lisp code can do. That means that when the compiler is running, the entire Lisp (run-time) system must be present.

We’ll stress this once more: at compile-time, you have the full language at your disposal.

Novices often make the following sort of mistake. Suppose that the setq2 macro needs to do some complex transformation on its e argument before plugging it into the result. Suppose this transformation can be written as a Lisp procedure some-computation. The novice will often write:

(defmacro setq2 (v1 v2 e) (let ((e1 (some-computation e))) (list 'progn (list 'setq v1 e1) (list 'setq v2 e1))))(defmacro some-computation (exp) ...) ;; _Wrong!_

The mistake is to suppose that once a macro is called, the Lisp system enters a “macro world,” so naturally everything in that world must be defined using defmacro. This is the wrong picture. The right picture is that defmacro enables a step into the ordinary Lisp world, but in which the principal object of manipulation is Lisp code. Once that step is taken, one uses ordinary Lisp function definitions:

(defmacro setq2 (v1 v2 e) (let ((e1 (some-computation e))) (list 'progn (list 'setq v1 e1) (list 'setq v2 e1))))(defun some-computation (exp) ...) ;; _Right!_

One possible explanation for this mistake may be that in other languages, such as C, invoking a preprocessor macro does get you into a different world; you can’t run an arbitrary C program. It might be worth pausing to think about what it might mean to be able to.

Another subtle consequence is that we must spell out how the arguments to the macro get distributed to the hypothetical behind-the-scenes function (called setq2-function in my example). In most cases, it is easy to do so: In defining a macro, we use all the usual lambda-list syntax, such as &optional, &rest, &key, but what gets bound to the formal parameters are pieces of the macro form, not their values (which are mostly unknown, this being compile time for the macro form). So if we defined a macro thus:

(defmacro foo (x &optional y &key (cxt 'null)) ...)

then

If we call it thus …The parameters’ values are …
(foo a)x=a, y=nil, cxt=null
(foo (+ a 1) (- y 1))x=(+ a 1), y=(- y 1), cxt=null
(foo a b :cxt (zap zip))x=a, y=b, cxt=(zap zip)

Note that the values of the variables are the actual expressions (+ a 1) and (zap zip). There is no requirement that these expressions’ values be known, or even that they have values. The macro can do anything it likes with them. For instance, here’s an even more useless variant of setq: (setq-reversible e1 e2 d) behaves like (setq e1 e2) if d=:normal, and behaves like (setq e2 e1) if d=:backward. It could be defined thus:

(defmacro setq-reversible (e1 e2 direction) (case direction (:normal (list 'setq e1 e2)) (:backward (list 'setq e2 e1)) (t (error "Unknown direction: ~a" direction))))

Here’s how it expands:

(macroexpand '(setq-reversible x y :normal))(SETQ X Y)T(macroexpand '(setq-reversible x y :backward))(SETQ Y X)T

And with a wrong direction:

(macroexpand '(setq-reversible x y :other-way-around))

We get an error and are prompted into the debugger!

We’ll see the backquote and comma mechanism in the next section, buthere’s a fix:

(defmacro setq-reversible (v1 v2 direction) (case direction (:normal (list 'setq v1 v2)) (:backward (list 'setq v2 v1)) (t `(error "Unknown direction: ~a" ,direction)))) ;; ^^ backquote ^^ comma: get the value inside the backquote.(macroexpand '(SETQ-REVERSIBLE v1 v2 :other-way-around));; (ERROR "Unknown direction: ~a" :OTHER-WAY-AROUND);; T

Now when we call (setq-reversible v1 v2 :other-way-around) we still get theerror and the debugger, but at least not when using macroexpand.

Before taking another step, we need to introduce a piece of Lisp notation that is indispensable to defining macros, even though technically it is quite independent of macros. This is the backquote facility. As we saw above, the main job of a macro, when all is said and done, is to define a piece of Lisp code, and that means evaluating expressions such as (list 'prog (list 'setq ...) ...). As these expressions grow in complexity, it becomes hard to read them and write them. What we find ourselves wanting is a notation that provides the skeleton of an expression, with some of the pieces filled in with new expressions. That’s what backquote provides. Instead of the the list expression given above, one writes

 `(progn (setq ,v1 ,e) (setq ,v2 ,e));;^ backquote ^ ^ ^ ^ commas

The backquote (`) character signals that in the expression that follows, every subexpression not preceded by a comma is to be quoted, and every subexpression preceded by a comma is to be evaluated.

You can think of it, and use it, as data interpolation:

`(v1 = ,v1) ;; => (V1 = 3)

That’s mostly all there is to backquote. There are just two extra items to point out.

Comma-splice ,@

First, if you write “,@e” instead of “,e” then the value of e is spliced (or “joined”, “combined”, “interleaved”) into the result. So if v equals (oh boy), then

(zap ,@v ,v)

evaluates to

(zap oh boy (oh boy));; ^^^^^ elements of v (two elements), spliced.;; ^^ v itself (a list)

The second occurrence of v is replaced by its value. The first is replaced by the elements of its value. If v had had value (), it would have disappeared entirely: the value of (zap ,@v ,v) would have been (zap ()), which is the same as (zap nil).

Quote-comma ‘,

When we are inside a backquote context and we want to print anexpression literally, we have no choice but to use the combination ofquote and comma:

(defmacro explain-exp (exp) `(format t "~S = ~S" ',exp ,exp)) ;; ^^(explain-exp (+ 2 3));; (+ 2 3) = 5

See by yourself:

;; Defmacro with no quote at all:(defmacro explain-exp (exp) (format t "~a = ~a" exp exp))(explain-exp v1);; V1 = V1;; OK, with a backquote and a comma to get the value of exp:(defmacro explain-exp (exp) ;; WRONG example `(format t "~a = ~a" exp ,exp))(explain-exp v1);; => error: The variable EXP is unbound.;; We then must use quote-comma:(defmacro explain-exp (exp) `(format t "~a = ~a" ',exp ,exp))(explain-exp (+ 1 2));; (+ 1 2) = 3

Nested backquotes

Second, one might wonder what happens if a backquote expression occurs inside another backquote. The answer is that the backquote becomes essentially unreadable and unwriteable; using nested backquote is usually a tedious debugging exercise. The reason, in my not-so-humble opinion, is that backquote is defined wrong. A comma pairs up with the innermost backquote when the default should be that it pairs up with the outermost. But this is not the place for a rant; consult your favorite Lisp reference for the exact behavior of nested backquote plus some examples.

Building lists with backquote

[…]

I said in the first section that my definition of setq2 wasn’t quite right, and now it’s time to fix it.

Suppose we write (setq2 x y (+ x 2)), when x=8. Then according to the definition given above, this form will expand into

(progn (setq x (+ x 2)) (setq y (+ x 2)))

so that x will have value 10 and y will have value 12. Indeed, here’s its macroexpansion:

(macroexpand '(setq2 x y (+ x 2)));;(PROGN (SETQ X (+ X 2)) (SETQ Y (+ X 2)))

Chances are that isn’t what the macro is expected to do (although you never know). Another problematic case is (setq2 x y (pop l)), which causes l to be popped twice; again, probably not right.

The solution is to evaluate the expression e just once, save it in a temporary variable, and then set v1 and v2 to it.

Gensym

To make temporary variables, we use the gensym function, which returns a fresh variable guaranteed to appear nowhere else. Here is what the macro should look like:

(defmacro setq2 (v1 v2 e) (let ((tempvar (gensym))) `(let ((,tempvar ,e)) (progn (setq ,v1 ,tempvar) (setq ,v2 ,tempvar)))))

Now (setq2 x y (+ x 2)) expands to

(let ((#:g2003 (+ x 2))) (progn (setq x #:g2003) (setq y #:g2003)))

Here gensym has returned the symbol #:g2003, which prints in this funny way because it won’t be recognized by the reader. (Nor is there any need for the reader to recognize it, since it exists only long enough for the code that contains it to be compiled.)

Exercise: Verify that this new version works correctly for the case (setq2 x y (pop l1)).

Exercise: Try writing the new version of the macro without using backquote. If you can’t do it, you have done the exercise correctly, and learned what backquote is for!

The moral of this section is to think carefully about which expressions in a macro get evaluated and when. Be on the lookout for situations where the same expression gets plugged into the output twice (as e was in my original macro design). For complex macros, watch out for cases where the order that expressions are evaluated differs from the order in which they are written. This is sure to trip up some user of the macro - even if you are the only user.

[…]

A gentle introduction to Compile-Time Computing — Part 1

Safely dealing with scientific units of variables at compile time (a gentle introduction to Compile-Time Computing — part 3)

The following video, from the series“Little bits of Lisp”by cbaggers, is a two hours long talkon macros, showing simple to advanced concepts such as compilermacros:https://www.youtube.com/watch?v=ygKXeLKhiTIIt also shows how to manipulate macros (and their expansion) in Emacs.

Common Lisp Macros By Example Tutorial - Lisp journey (1)

Common Lisp Macros By Example Tutorial - Lisp journey (2024)

FAQs

What is the Common Lisp macro system? ›

The Common Lisp macro facility allows the user to define arbitrary functions that convert certain Lisp forms into different forms before evaluating or compiling them. This is done at the expression level, not at the character-string level as in most other languages.

When to use Lisp macros? ›

Sometimes, using macros instead of functions will make a program more closely integrated with Lisp. Instead of writing a program to solve a certain problem, you may be able to use macros to transform the problem into one that Lisp already knows how to solve.

Are rust macros like Lisp macros? ›

Rust has a couple of couple of different kinds of macros: declarative macros that pattern-match on arguments to emit code; and procedural macros that perform more general code-to-code transformations. Lisp has only one kind that operates from code to code.

What is the Common Lisp push macro? ›

Simplified Common Lisp reference - push. PUSH macro modifies variable or generally place. It makes a new cons cell filled with item as car and previous value as cdr, that is effectively prepends new item to list found at the place.

Is Common Lisp still used for AI? ›

Lisp is a niche language: Lisp is not as widely used as other programming languages like Python or Java, so the demand for Lisp programmers may be more limited. However, many industries and applications still use Lisp, such as artificial intelligence, natural language processing, and computer algebra systems.

Is Common Lisp as fast as C? ›

Contrary to popular belief, Lisp code can be very ef- ficient today: it can run as fast as equivalent C code or even faster in some cases.

What are the three functions required by Lisp? ›

After the rules of syntax and semantics, the three most basic components of all Lisp programs are functions, variables and macros. You used all three while building the database in Chapter 3, but I glossed over a lot of the details of how they work and how to best use them.

How do I know what macros to use? ›

Calculate Your Macros
  1. Carbohydrates: Daily calories * 0.40 / 4 = grams of carbs per day.
  2. Proteins: Daily calories * 0.30 / 4 = grams of proteins per day.
  3. Fats: Daily calories * 0.30 / 9 = grams of fats per day.
Mar 11, 2024

What is the difference between macros in racket and Common Lisp? ›

A macro in Common Lisp is a function that runs at compile time, accepting symbols as input and injecting them into a template to produce new code. Macros in Racket, on the other hand, rely on the concept of hygiene. They can handle Common Lisp-style macros, but also more elaborate syntax rearrangements.

What is the power of common Lisp? ›

The Common Lisp Object System (CLOS) is a powerful object-oriented programming system that is built into the language. This enables developers to write sophisticated object-oriented programs without needing to use external libraries or frameworks.

Can you get banned in Rust for macros? ›

Yes, macros are bannable.

Are macros bad in Rust? ›

The drawback is that macro-based code can be harder to understand, because fewer of the built-in rules apply. Like an ordinary function, a well-behaved macro can be used without understanding its implementation. However, it can be difficult to design a well-behaved macro!

What are Lisp macros good for? ›

Macros are for making syntactic extensions to Lisp. One often hears it said that macros are a bad idea, that users can't be trusted with them, and so forth. Balderdash. It is just as reasonable to extend a language syntactically as to extend it by defining your own procedures.

What is the best IDE for Common Lisp? ›

Emacs
  • Emacs is a general-purpose editor and the most widely-used environment for Common Lisp development.
  • SLIME is the most widely-used Common Lisp IDE.
  • Portacle is a portable and multiplatform development environment. ...
  • Sly is a fork of SLIME.
  • SLIMV is a full-blown environment for Common Lisp inside of Vim.

What is the most popular Lisp programming language? ›

Today, the best-known general-purpose Lisp dialects are Common Lisp, Scheme, Racket, and Clojure.

What is the Common Lisp condition system? ›

The condition system allows the program to deal with exceptional situations, or situations which are outside the normal operation of the program as defined by the programmer. A common example of an exceptional situation is an error, however the Common Lisp condition system encompasses much more than error handling.

What is the most common type of Lisp? ›

The most common type of lisp is the interdental lisp. The reason it's called “interdental” is because the tongue protrudes between the teeth, like this: With an interdental lisp, you'll hear more of a /th/ sound instead of an /s/ or /z/. The word “soon” would sound like “thoon,” and “zipper” would sound like “thipper.”

What is the most Common Lisp language? ›

Today, the best-known general-purpose Lisp dialects are Common Lisp, Scheme, Racket, and Clojure. Lisp was originally created as a practical mathematical notation for computer programs, influenced by (though not originally derived from) the notation of Alonzo Church's lambda calculus.

What is the common lisp music software? ›

CLM (originally an acronym for Common Lisp Music) is a music synthesis and signal processing package in the Music V family created by Bill Schottstaedt.

References

Top Articles
Download handyPrint for Mac | MacUpdate
Petsmart Donations Request
Netronline Taxes
Repentance (2 Corinthians 7:10) – West Palm Beach church of Christ
<i>1883</i>'s Isabel May Opens Up About the <i>Yellowstone</i> Prequel
Dr Lisa Jones Dvm Married
Umn Pay Calendar
Kentucky Downs Entries Today
Riegler &amp; Partner Holding GmbH auf LinkedIn: Wie schätzen Sie die Entwicklung der Wohnraumschaffung und Bauwirtschaft…
Which aspects are important in sales |#1 Prospection
Degreeworks Sbu
Enderal:Ausrüstung – Sureai
Lenscrafters Huebner Oaks
Rainfall Map Oklahoma
State HOF Adds 25 More Players
Wisconsin Women's Volleyball Team Leaked Pictures
Concordia Apartment 34 Tarkov
Military life insurance and survivor benefits | USAGov
Www.craigslist.com Savannah Ga
Ford F-350 Models Trim Levels and Packages
Mtr-18W120S150-Ul
Japanese Mushrooms: 10 Popular Varieties and Simple Recipes - Japan Travel Guide MATCHA
A Person That Creates Movie Basis Figgerits
Routing Number For Radiant Credit Union
Jordan Poyer Wiki
Reser Funeral Home Obituaries
Airline Reception Meaning
Milwaukee Nickname Crossword Clue
Idle Skilling Ascension
Busted Mugshots Paducah Ky
Pulitzer And Tony Winning Play About A Mathematical Genius Crossword
HP PARTSURFER - spare part search portal
Franklin Villafuerte Osorio
Gus Floribama Shore Drugs
Dentist That Accept Horizon Nj Health
15 Downer Way, Crosswicks, NJ 08515 - MLS NJBL2072416 - Coldwell Banker
Shiftwizard Login Johnston
Walter King Tut Johnson Sentenced
Tamilrockers Movies 2023 Download
Daily Jail Count - Harrison County Sheriff's Office - Mississippi
Are you ready for some football? Zag Alum Justin Lange Forges Career in NFL
The Closest Walmart From My Location
Why I’m Joining Flipboard
Husker Football
manhattan cars & trucks - by owner - craigslist
Tripadvisor Vancouver Restaurants
Smite Builds Season 9
Vagicaine Walgreens
Mlb Hitting Streak Record Holder Crossword Clue
What Is The Gcf Of 44J5K4 And 121J2K6
Dumb Money Showtimes Near Regal Stonecrest At Piper Glen
Cognitive Function Test Potomac Falls
Latest Posts
Article information

Author: Edmund Hettinger DC

Last Updated:

Views: 6392

Rating: 4.8 / 5 (78 voted)

Reviews: 93% of readers found this page helpful

Author information

Name: Edmund Hettinger DC

Birthday: 1994-08-17

Address: 2033 Gerhold Pine, Port Jocelyn, VA 12101-5654

Phone: +8524399971620

Job: Central Manufacturing Supervisor

Hobby: Jogging, Metalworking, Tai chi, Shopping, Puzzles, Rock climbing, Crocheting

Introduction: My name is Edmund Hettinger DC, I am a adventurous, colorful, gifted, determined, precious, open, colorful person who loves writing and wants to share my knowledge and understanding with you.