Slide 1

Slide 1 text

MACROS
  ALL
   THE
    WAY
     DOWN Chris Jester-Young
 cky#6493 July 2018

Slide 2

Slide 2 text

LEVEL 0

Slide 3

Slide 3 text

For Scheme purists My talk will cover some Racket-specific features. Some of these features have been ported to other Schemes. If your Scheme doesn’t have them, I’m sorry. I grew up in the world of syntax objects (Guile and Racket mainly). If your Scheme doesn’t use syntax objects, I’m sorry.

Slide 4

Slide 4 text

Cheatsheet for Clojurians Racket Clojure (let ([x 1] [y 2])
 (+ x y)) (let [[x y] [1 2]]
 (+ x y)) (let* ([x 1] [y 2])
 (+ x y)) (let [x 1 y 2]
 (+ x y)) '(foo bar baz) '(foo bar baz) `(foo ,(+ 42 10)) `(~'foo ~(+ 42 10)) `(foo ,@(map sqrt '(1 2 3))) `(~'foo ~@(map #(Math/sqrt %)
 '(1 2 3))) (foo #:bar 42 #:baz 10) (foo :bar 42 :baz 10) (lambda (foo #:bar [bar 42])
 (+ foo bar)) (fn [foo & {bar :bar
 :or {bar 42}}]
 (+ foo bar)) Go to https://goo.gl/jkjSAN to keep this on hand.

Slide 5

Slide 5 text

More tips for Clojurians Commas are not whitespace: ,x reads as (unquote x). ,@x reads as (unquote-splicing x). `x reads as (quasiquote x). In Scheme, quasiquote is a macro, not a reader macro.

Slide 6

Slide 6 text

Racket-specific goodies In Racket, square and curly brackets are treated the same as round brackets by default. (foo bar), [foo bar], and {foo bar} are treated the same. Vector literals are written as #(a b c), and hash literals are written as #hash((k1 . v1) (k2 . v2) (k3 . v3)). Vector and hash literals are quoted by default. Use quasiquote if you want dynamic values.

Slide 7

Slide 7 text

LEVEL −1

Slide 8

Slide 8 text

And here we are (and (odd? x)
 (even? y)) 㱺 (if (odd? x)
 (even? y)
 #f) Macro code (define-syntax-rule (and expr1 expr2)
 (if expr1 expr2 #f)) In Scheme, true is written as #t and false is written as #f. #f is the only falsy value, and every other value is truthy. (Later on we will cover how to write a variadic version of this macro.)

Slide 9

Slide 9 text

Or so I hear (or (even? x)
 (odd? y)) 㱺 (let ((x (even? x)))
 (if x
 x
 (odd? y))) Macro code (define-syntax-rule (or expr1 expr2)
 (let ((x expr1)) (if x x expr2))) Scheme macros are hygienic. Here, the x in the expansion is treated as a different identifier from the x in expr1. There is no need to explicitly use gensyms.

Slide 10

Slide 10 text

Let there be light (let ([a 20]
 [b 42]
 [c 100])
 (+ a b c)) 㱺 ((lambda (a b c)
 (+ a b c))
 20 42 100) (define-syntax-rule
 (let ([id value] ...)
 body0 body* ...)
 ((lambda (id ...)
 body0 body* ...)
 value ...)) Macro code

Slide 11

Slide 11 text

LEVEL −2

Slide 12

Slide 12 text

Sweet syntactic sugar We’ve seen define-syntax-rule so far. It is actually a macro that uses define-syntax and syntax-rules behind the scenes: (define-syntax-rule
 (and expr1 expr2)
 (if expr1 expr2 #f)) 㱺 (define-syntax and
 (syntax-rules ()
 [(and expr1 expr2)
 (if expr1 expr2 #f)])) For simple macros, define-syntax-rule usually suffices. For something more complex, we need to use syntax-rules directly.

Slide 13

Slide 13 text

Again and again Armed with syntax-rules, we can use recursion to implement a variadic version of the and macro: (define-syntax and
 (syntax-rules ()
 [(and) #t]
 [(and expr) expr]
 [(and expr rest ...)
 (if expr
 (and rest ...)
 #f)]))

Slide 14

Slide 14 text

And again and again This defines a recursive and macro with 3 cases: No expressions: and is defined to return true. One expression: simply evaluate the expression. Multiple expressions: if the first expression is truthy, then recurse on the remainder of the expressions, otherwise return false.

Slide 15

Slide 15 text

Proof of the pudding (and (odd? x)
 (even? y)
 (positive? z)) 㱺 (if (odd? x)
 (if (even? y)
 (positive? z)
 #f)
 #f) Note that the single-expression case is required to avoid having (and (positive? z)) expand into (if (positive? z) #t #f). You can implement a variadic or macro using a similar technique. But there is another way to implement or, using cond, which we’ll look at later.

Slide 16

Slide 16 text

Speaking of cond As you probably know, cond is a macro implemented on top of if. Let’s try our hand at writing a simple version! For now, let’s assume all cond cases are of either the (test expr) form or the (else expr) form.

Slide 17

Slide 17 text

Here’s my version (define-syntax cond
 (syntax-rules (else)
 [(cond) (void)]
 [(cond (else expr)) expr]
 [(cond (test expr) rest ...)
 (if test
 expr
 (cond rest ...))])) This version is very simple and leaves a lot to be desired in terms of user-friendly error handling. But it’s a reasonable starting point.

Slide 18

Slide 18 text

LEVEL −3

Slide 19

Slide 19 text

Procedural macros So far, we’ve been looking at declarative macros only. These work for simple cases, but more complex macros require real code. Classic Lisp macros work with datums. Scheme macros work with syntax objects. Attaches additional context to allow macro hygiene to work transparently.

Slide 20

Slide 20 text

And here we go again Syntax transformers are simply functions that receive a syntax object and return another syntax object. Here, we’ll implement and using a procedural macro: (define-syntax and
 (lambda (stx)
 (syntax-case stx ()
 [(_) #'#t]
 [(_ expr) #'expr]
 [(_ expr rest ...)
 #'(if expr
 (and rest ...)
 #f)])))

Slide 21

Slide 21 text

#'? What on earth? Racket (and other Scheme implementations that use syntax objects) has 4 extra reader macros: #'foo reads as (syntax foo). #`foo reads as (quasisyntax foo). #,foo reads as (unsyntax foo). #,@foo reads as (unsyntax-splicing foo). Obviously, these are syntax object analogues for quote, quasiquote, unquote, and unquote-splicing.

Slide 22

Slide 22 text

Or else? (define-syntax or
 (lambda (stx)
 (define (build-cond-clauses exprs)
 (syntax-case exprs ()
 [(expr) #'((else expr))]
 [(expr rest ...)
 #`((expr) #,@(build-cond-clauses #'(rest ...)))]))
 
 (syntax-case stx ()
 [(or) #'#f]
 [(or expr ...)
 #`(cond #,@(build-cond-clauses #'(expr ...)))])))

Slide 23

Slide 23 text

Questions? This barely scratches the surface of writing macros, but should get you started. I could easily get to Level −20 and beyond if I were to keep writing these slides! I’m always happy to answer any questions you have about writing macros, both now and later. You can email me at [email protected], though I'm easier to reach on Discord at cky#6493.