Last active
March 8, 2025 16:11
-
-
Save sebastiancarlos/c25b0fb656c1ee67df260e3c24642c70 to your computer and use it in GitHub Desktop.
Explanation of the ONCE-ONLY Common Lisp macro.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; Deus Vult, Compilamus | |
;; The following code is compliant with the Holy See's ANSI Standard Of 2054. | |
:; Here's an example of a classic Lisp macro, ONCE-ONLY, which ensures | |
;; parameters to a Lisp macro are executed only once, along with a dense | |
;; explanation. | |
;; Example implementation of ONCE-ONLY | |
(defmacro do-primes ((var start end) &body body) | |
(once-only (start end) | |
`(do ((,var (next-prime ,start) (next-prime (1+ ,var)))) | |
((> ,var ,end)) | |
,@body))) | |
(defmacro once-only ((&rest names) &body body) | |
(let ((gensyms (loop for n in names collect (gensym)))) | |
`(let (,@(loop for g in gensyms collect `(,g (gensym)))) | |
`(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n))) | |
,(let (,@(loop for n in names for g in gensyms collect `(,n ,g))) | |
,@body))))) | |
;; Explanation of ONCE-ONLY: | |
;- This classic macro is used to generate code that evaluates certain macro | |
; arguments once only and in a particular order. | |
; - It can be a bit tricky to follow, so keep in mind the following: | |
; - When you see a double-backquote (``), we are generating code with a | |
; single backquote. | |
; - Even if the backquotes are not back to back, it still usually means | |
; that you are generating code which, at some point, contains an internal | |
; backquote. | |
; - Observing multiple commas (,,) is also a sign of this (more on | |
; multiple commas later) | |
; - Sometimes, the ",@" form is used even if evaluating into a containing | |
; list | |
; - For example "(,@(list 1 2))" is the same as ",(list 1 2)", as both | |
; evaluate to "(1 2)" | |
; - However, the ",@" syntax might be preferred as it's more explicit | |
; about the fact that we are inserting multiple forms on a given space. | |
; - Nested backquotes (and multiple commas): | |
; - We can think of it like this: | |
; - only one backquote gets collapsed per evaluation (the outermost one) | |
; - If there are multiple commas, the leftmost one belongs to the | |
; leftmost backquote. | |
; - Substitutions are made only for unquoted components appearing at the | |
; same nesting level as the outermost backquote | |
; - Keep the following in mind regarding "macros" and "macro-writing macros" | |
; - A macro contains: | |
; - 1. (Optional) Extra code which runs at "macro expansion time", which | |
; helps produce 2. | |
; - 2. Result of the macro. It's the code which should run at "runtime" | |
; when calling the macro. Usually it's a backquote expression, so that | |
; the macro parameters can be evaluated into it. | |
; - A macro-writing macro contains: | |
; - 1. (Optional) Extra code which runs at "macro-writing-macro expansion | |
; time", which helps produce 2. | |
; - 2. Result of the macro-writing macro. Usually it's a backquote | |
; expression, so that the macro-writing macro parameters can be | |
; evaluated into the code. 2 itself consists of both 3 and 4. | |
; - 3. (Optional) Extra code which runs at "macro expansion time", which | |
; helps produce 4. | |
; - 4. Result of the macro. Code which runs at "runtime". Usually it's a | |
; backquote expression, so that the macro parameters AND/OR | |
; macro-writing macro parameters can be evaluated into it. | |
; - An explanation of ONCE-ONLY would be: | |
; - Note that, on usage, you should pass a body to ONCE-ONLY, which is a | |
; backquoted form containing unqoutes of the uses of the variables passed | |
; - First, a list of gensyms is created for every name passed. This is done | |
; at "macro-writing macro expansion time" | |
; - Indeed, two rounds of generating gensyms are necessary because | |
; once-only is a macro itself, and so it has to be hygienic for its own | |
; sake. And so, this first "gensyms" is used for "once-only" itself. | |
; - A backquote is evaluated, containing code to run at "macro expansion | |
; time" | |
; - This code instantiates the original gensyms to new gensyms at | |
; "macro-expansion time". The new gensyms will be used on the macro. | |
; - Then, it returns a final backquote, which will be what runs at runtime. | |
; - It instantiates the macro gensyms to the parameters to the macro | |
; - At macro expansion time. it creates a final instantiation of the names | |
; to the values of the names, so that the runtime body can use them as | |
; expected, God willing. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment