Post

Macros

Macros: The Key Points


Macros let the programmers to add their own synatactic sugar to the syntax of a language. A macro definition introduces some new syntax into the language. It describes how to transform the new syntax into different syntax in the language itself. A macro system is a language (or part of a larger languages) for defining macros. A macro use is just using one of the macros previously defined. The semantics of a macro use is to replace the macro use with the appropriate syntax as defined by the macro definition. This process is often called macro expansion because it is common but not required that the syntactic transformation produces a larger amount of code.


The key point is that macro expansion happens before anything else: before type- checking, before compiling, before evaluation. Think of “expanding all the macros” as a pre-pass over your entire program before anything else occurs. So macros get expanded everywhere, such as in function bodies, both branches of conditionals, etc.


Macro examples in Racket:


A macro so that programmers can write (my-if e1 then e2 else e3) where my-if, then, and else are keywords and this macro-expands to (if e1 e2 e3).

A macro so that programmers can write (comment-out e1 e2) and have it transform to e2, i.e., it is a convenient way to take an expression e1 out of the program (replacing it with e2) without actually deleting anything.

A macro so that programmers can write (my-delay e) and have it transform to (mcons #f (lambda () e)). This is different from the my-delay function we defined earlier because the function required the caller to pass in a thunk. Here the macro expansion does the thunk creation and the macro user should not include an explicit thunk.

Tokenization, Parenthesization, and Scope


Tokenization


Let’s consider a macro that, “replaces every use of head with car”. In macro systems, that does not mean some variable headt would be rewritten as cart. So the implementation of macros has to at least understand how a programming language’s text is broken into tokens (i.e., words). This notion of tokens is different in different languages. For example, a-b would be three tokens in most languages (a variable, a subtraction, and another variable), but is one token in Racket.

In C, “macro expand a to z”, a-b would be z-b where it is not token but subtraction.


Parenthesization


In C or C++, if you have a macro

#define ADD(x,y) x+y

then ADD(1,2/3)*4 gets rewritten as 1 + 2 / 3 * 4, which is not the same thing as (1 + 2/3)*4. So in such languages, macro writers generally include lots of explicit parentheses in their macro definitions, e.g.,

#define ADD(x,y) ((x)+(y))

In Racket, macro expansion preserves the code structure so this issue is not a problem.


Scope


In Racket local variables can shadow macros. Macro expansion don’t happen when creating variable bindings. If local bindings would not happen to shadow macros:

“macro expand head to car”:

1
2
(let  ([head 0] [car 1]) head) ;0
(let* ([head 0] [car 1]) head) ;0

Would become:

1
2
(let  ([car 0] [car 1]) head) ;error / gives error because we are not allowed declare the same variable twice in the same let expression.
(let* ([car 0] [car 1]) head) ;0     / the output is 1 because second "car" is shadowing the first "car", but we meant to refer first "car" which has value "0"

This is why C/C++ convention is all-caps macros and non-all-caps for everything else


Racket Macros With define-syntax

1
2
3
4
5
6
7
;; a cosmetic macro -- adds then, else
(define-syntax my-if
   (syntax-rules (then else)
	[(my-if e1 then e2 else e3)
	 (if e1 e2 e3)]))

;  (my-if foo then bar else baz) -> (if foo bar baz)
  • define-syntax is the special form for defining a macro

  • my-if is the name of our macro. It adds my-if to the environment so that expressions of the form (my-if …) will be macro-expanded according to the syntax rules in the rest of the macro definition.

  • syntax-rules is a keyword

  • The next parenthesized list (in this case (then else)) is a list of “keywords” for this macro, i.e., any use of then or else in the body of my-if is just syntax whereas anything not in this list (and not my-if itself) represents an arbitrary expression.

  • The rest is a list of pairs: how my-if might be used and how it should be rewritten if it is used that way.

  • In this example, our list has only one option: my-if must be used in an expression of the form (my-if e1 then e2 else e3) and that becomes (if e1 e2 e3).

1
2
3
4
5
6
7
8
;; a macro to replace an expression with another one

(define-syntax comment-out
   (syntax-rules ()
	[(comment-out e1 e2) e2]))

;  (comment-out (car null) (+ 3 4)) -> (+ 3 4)

1
2
3
4
5
6
7
;; A delay macro

(defie-syntax my-delay
	(syntax-rules ()
	[(my-delay e) (mcons #f (lambda () e))]))

(f (my-delay e))
This post is licensed under CC BY 4.0 by the author.