Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Macros All the Way Down

Macros All the Way Down

Presented at TriClojure meetup on 2018-07-26.

This talk gives a brief introduction to Scheme macros to people familiar with Clojure. Although some of the code is Racket-oriented, they are easily adaptable to work with many other Scheme implementations.

Chris Jester-Young

July 26, 2018
Tweet

More Decks by Chris Jester-Young

Other Decks in Programming

Transcript

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.)
  6. 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.
  7. 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
  8. 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.
  9. 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)]))
  10. 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.
  11. 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.
  12. 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.
  13. 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.
  14. 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.
  15. 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)])))
  16. #'? 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.
  17. 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 ...)))])))
  18. 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.