2 Macro Expansion
Goals |
— |
— |
— |
To understand Racket’s approach to language extension and construction, we need to have a model of the front-end of a language implementation.
;; A LAM is one of: Its Interpretation is: |
;; -- VAR -- a variable ref |
;; -- NUM -- a literal constant |
;; -- (lambda (VAR) LAM) -- a 1-argument function |
;; -- (+ LAM LAM) -- an addition expression |
;; -- (LAM LAM) -- a function application |
;; |
;; NUM is a (Racket) number |
;; |
;; VAR is a sequence of keyboard characters (not a Racket number) |
These first five lines represent the core language of LAM, as in, "understood by the compiler, which assigns meaning."
;; -- (let-syntax (VAR META) LAM) -- a macro definition |
;; -- (VAR LAM ...) -- a macro use IF VAR is declared |
;; |
;; META is a language for writing down functions that construct LAMs |
Let’s study lexing, parsing, and macro expansion in this context.
2.1
macro.rkt: provides functions to state and run simple macros
table.rkt: provides functions for adding VAR0keyed information to, and retrieving it from, a table
2.2 A Model of (plain old) Lisp Parsing and Macro Expanding
Let’s start with examples in the plain language state as strings:
(define program-as-text "((lambda (x) (+ x 10)) 42)") (define macro-as-text (string-append "((lambda (y)" " (let-syntax (plus10" " (lambda (stx)" " (match stx" " [`(plus10 ,x) `(+ ,x 10)])))" " (plus10 y)))" " 42)")) (define another-example (string-append "((lambda (fun)" " (fun " " (let-syntax (syn (lambda (stx)" " 23))" " (syn (fun 1) 20))))" " (lambda (x) (+ x 1)))"))
(struct Syntax (e {stuff #:auto}) #:transparent #:auto-value 'stuff)
; A SyntaxTree is one of: ; – (Syntax Number) ; – (Syntax Symbol) ; – (Syntax (list SyntaxTree ... SyntaxTree))
; S-expression -> SyntaxTree (define (to-Syntax stx) (cond [(cons? stx) (Syntax (map to-Syntax stx))] [else (Syntax stx)]))
; String -> S-exp ; read string (of characters) and transform into an S-expression (define (lex str) ; also known as READER ... read ... to-Syntax ...)
(define (parse s table) ; also known as EXPANDER (match (Syntax-e s) [(? symbol?) s] [(? number?) s] [`(,(Syntax lambda _) ,(Syntax `(,(Syntax (? symbol? parameter) _)) _) ,body) (Syntax (list 'lambda parameter (parse body table)))] [`(,(Syntax '+ _) ,lop ,rop) (Syntax (list '+ (parse lop table) (parse rop table)))] ... *** ... [`(,fun ,arg) (Syntax (list 'app (parse fun table) (parse arg table)))] [_ (error 'parse "~s is not grammatical" s)]))
[`(,(Syntax 'let-syntax _) ,(Syntax `(,(Syntax (? symbol? name) _) ,rhs) _) ,body) (define transformer (make-transformer (from-Syntax rhs))) (parse body (extend-table name transformer table))] [`(,(Syntax (? (compose (in? table)) m) _) ,more ...) (define transformer (retrieve m table)) (parse (transformer s) table)]
The make-tranformer function turns the rhs META expression into a function on SyntaxTree.
With extend-table, parse remembers the association between name and this function for just the body of let-syntax. The last match clause of parse shows how the macro is used when a particular macro shape is discovered.
And yes, you can test this on the above strings.
2.3 What Does This Look Like in Real Racket?
#lang racket ((lambda (fun) (fun (let-syntax ([syn (lambda (stx) ; fix 1: add [...] #'23)]) ; fix 2: add syntax (syn (fun 1) 20)))) (lambda (x) (+ x 1)))
When you expand this expression in the model, the inner application of
fun goes away, because the syn function is a constant
function. When you expand this expression in Racket with expand
the result is almost the same—
#lang racket (let ([fun (lambda (x) 65)]) (list (fun 0) (let-syntax ((syn (lambda (stx) #'23))) (list (syn) (syn 10) (syn 10 20))) (fun 1)))
Add (displayln stx) to the syn function to see what is handed over as the argument. Then play with syntax-e to extract some of the tree’s pieces and look at them.
The point of the first two days is to explain the vast machinery of tools we have to define functions such as syn with notational economy.
#lang racket (begin-for-syntax (define fun-defined-at-compile-time (lambda (stx) #'23))) (let ([fun (lambda (x) 65)]) (list (fun 0) (let-syntax ((syn fun-defined-at-compile-time)) (list (syn) (syn 10) (syn 10 20))) (fun 1)))
#lang racket (begin-for-syntax (define fun-defined-at-compile-time (lambda (stx) #'23))) (define-syntax syn fun-defined-at-compile-time) (let ([fun (lambda (x) 65)]) (list (fun 0) (list (syn) (syn 10) (syn 10 20)) (fun 1)))
2.3.1 Scope
#lang racket (let ([fun (lambda (x) 65)]) (list (fun 0) (let-syntax ((fun (lambda (stx) #'23))) (list (fun) (fun 10) (fun 10 20))) (fun 1)))
#lang racket (let ([fun (lambda (x) 65)]) (list (fun 0) (let-syntax ((fun (lambda (stx) #'(fun 23)))) (list (fun) (fun 10) (fun 10 20)))))