1 of 20

Lecture 18

Macros

Programming Languages

UW CSE 341 - Spring 2021

2 of 20

Macros

  • A macro definition describes how to transform syntax
    • A [different kind of] function from syntax to syntax
    • Runs at “compile time” -- before “regular” evaluation starts
  • A macro is one way to implement syntactic sugar
    • Analogy from OCaml: replace e1 && e2 with if e1 then e2 else false
  • A macro system is a language (or part of a larger language) for defining macros
  • Macro expansion is the process of rewriting syntax for each macro use
    • Before a program is run (or even compiled)

3 of 20

Macro expansion

  • Macro expansion: “run” a macro definition
    • Replace patterns a definition matches with what they expand to
    • Transform the syntax of the original program according to all macro definitions
    • “Expansion” does not necessarily mean “make longer”, i.e., a misnomer
  • Need to remember what happens when!
    • Macros change the syntax of the program before it gets run (!)
    • For example, even inside function bodies

4 of 20

Racket Macros

  • When you define a macro m
    • m becomes a special form (so it is NOT a function call!)
    • Anywhere it appears in the program (“gets used”), it will be expanded according to its definition
  • Example ideas for macros:
    • Transform (my-if e1 then e2 else e3) into (if e1 e2 e3)
    • Transform (comment-out e1 e2) into e2
    • Transform (my-delay e) into (mcons #f (lambda () e))

5 of 20

Example Uses

  • It’s like we added our own keywords to the language, but
    • Keywords “scoped” to the macro they are part of
    • Syntax error if keywords are misused

6 of 20

When Should We Use Macros?

  • Requires judgement based on experience
    • Folks tend to overuse them, especially if they just learned about them :-)

  • Rough rule of thumb:
    • Only use a macro when a function cannot do what you want
    • And what you want helps avoid misuse without introducing worse potential misuse

7 of 20

Three Questions + Macro Mechanics

  • How do tokens, parens, and scope work for macros in general?
  • How can we define our own macros in Racket?
  • Potential evaluation order gotchas
  • Potential scope/shadowing (non-)gotchas

8 of 20

Question 1: How to Tokenize?

  • Macro languages work on the level of tokens and syntax
    • Not on the level of characters/substrings in your source!
    • We need to know how the language tokenizes text
  • Example: suppose we have a macro that expands head into car
    • Would NOT rewrite (+ headt 7) into (+ cart 7)
    • Would NOT rewrite head-door into car-door
      • Though it would in C or OCaml where head-door is three tokens (subtraction)

9 of 20

Question 2: How to parenthesize (associativity)?

  • In languages with macro systems like C/C++: #define ADD(x, y) x + y
    • Behaves very poorly! ADD(1, 2*3) * 4 expands to 1 + 2*3 * 4 NOT (1 + 2*3) * 4
  • C “solution” : macro writers use lots of parens or users suffer pain
    • #define ADD(x, y) ((x) + (y))
  • Racket macros do not have this problem because Racket’s syntax does not have this problem

10 of 20

Question 3: How to interact with scope?

  • Imagine that macros in Racket also applied to variable bindings

  • In C, often use ALL CAPS for macro names and only macro names to avoid “shadowing”
  • Racket provides a better, more principled solution that “gets scope right”

11 of 20

Example Racket Macro Definitions

12 of 20

Revisiting Promises

13 of 20

Defining our own macro for delay

  • Crucially depends on macros transforming syntax (need to delay eval!)
  • Cannot be done with a function!
  • Changes how clients use my-delay (no thunks from client, else incorrect double-thunking)

14 of 20

Should force also be a macro? NO!

  • We could define it as a macro, but there is no benefit!
  • Force evaluates argument only once (see x below), as desired
  • But that’s exactly what a function would do!

15 of 20

Another BAD macro

  • These two functions are equivalent to callers:

    • (these should not be made into macros, but just to illustrate)
  • NOT equivalent as macros anymore!

16 of 20

Be careful when copying syntax!

  • Think about whether you really want macro to evaluate something > 1 time
    • If not, use a local variable binding!

  • Also think carefully about evaluation order! (Probably want to preserve left-to-right.)

17 of 20

Local variables in macros? What about shadowing?!

  • Very bad idea in C -- folks use weird names instead to avoid shadowing
  • Works just fine in Racket though!

  • Racket’s hygienic macros mean we don’t get the “wrong” naive expansion

18 of 20

Shadowing “the other way”

  • This also looks wrong, but again Racket gets it right!

  • Naive expansion would do something weird:

19 of 20

Hygiene

  • Behind the scenes, Racket renames local variables in macros
  • Look up non-local macro variables in environment where the macro was defined
    • Lexical scope for macros!
  • Naive expansion does neither of these
    • Hygiene is more-complicated but more-principled semantics
  • Sometimes (rarely) we don’t want hygienic macros
    • Racket has support for this too if you are curious

20 of 20

More examples

See the code for a few more examples of Racket macros:

  • A for loop for executing a body a fixed number of times
    • Shows a macro that purposely re-evaluates some expressions and not others
  • A fewer-parens variant of let* that supports 0, 1, or 2 bindings
  • A re-implementation of let* in terms of let
    • A recursive macro
    • A macro that takes any number of arguments