For example, here's a simple CL macro:
(defmacro defer (cleanup &body action)
`(unwind-protect (progn ,@action) ,cleanup))
`unwind-protect` is like a `try/finally` in other languages, and I used that above to create something similar to `defer` in Go/Zig which is related to it, but with the operands inverted.The ` symbol is a quasiquote. Unlike quote `'` it lets you unquote symbols inside with the , operator. That's why you'll always see a bunch of '`' and ',' in macros.
The @, thing is a "spread" (looks different in Racket from what I saw in the post, which used `...`). It just spreads whatever was on the list in the place you put that on, so if `action` is `(p 1) (p 2)`, then `(progn ,@action)` becomes `(progn (p 1) (p 2))`.
You can inspect what the actual code that will be compiled looks like with `macroexpand`:
CL-USER> (macroexpand '(defer (cleanup) (do-something)))
(UNWIND-PROTECT (PROGN (DO-SOMETHING)) (CLEANUP))
`progn` is a "special operator" that's needed when you want more than one expression to be evaluated in order.Example calling the macro:
CL-USER> (defer (print "done")
(print "hello")
(print "again"))
"hello"
"again"
"done"
As you can see, it executed the deferred expression last.We can prove that macros disappear after compile-time with an example:
CL-USER> (disassemble (lambda (x) (+ x x)))
; disassembly for (LAMBDA (X))
; Size: 36 bytes. Origin: #x8005F70664 ; (LAMBDA (X))
; 64: AA0A40F9 LDR R0, [THREAD, #16] ; binding-stack-pointer
; 68: AA0B00F9 STR R0, [CFP, #16]
; 6C: EA030CAA MOV R0, R2
; 70: EB030CAA MOV R1, R2
; 74: 297E80D2 MOVZ TMP, #1009
; 78: 5E6B69F8 LDR LR, [NULL, TMP] ; SB-KERNEL:TWO-ARG-+
; 7C: DE130091 ADD LR, LR, #4
; 80: C0031FD6 BR LR
; 84: E00120D4 BRK #15 ; Invalid argument count trap
You can see the Assembly is very simple for `(+ x x)` (the ADD instruction plus a bunch of stack/error maintenance).If we instead had a macro that did this:
(defmacro my-macro (x) `(+ ,x ,x))
And a function used that: (defun f (x) (my-macro x))
Now, disassembling the function: CL-USER> (disassemble #'f)
; disassembly for F
; Size: 36 bytes. Origin: #x8005C00374 ; F
; 74: AA0A40F9 LDR R0, [THREAD, #16] ; binding-stack-pointer
; 78: AA0B00F9 STR R0, [CFP, #16]
; 7C: EA030CAA MOV R0, R2
; 80: EB030CAA MOV R1, R2
; 84: 297E80D2 MOVZ TMP, #1009
; 88: 5E6B69F8 LDR LR, [NULL, TMP] ; SB-KERNEL:TWO-ARG-+
; 8C: DE130091 ADD LR, LR, #4
; 90: C0031FD6 BR LR
; 94: E00120D4 BRK #15 ; Invalid argument count trap
Same thing exactly.I don't know Racket, but knowing it can compile to binary, I expect macros in Racket would work exactly the same.