3 Onward to Racket
Goals |
— |
— |
3.1 What Does this Look Like in Real Racket?
#lang racket ((lambda (fun) (fun (let-syntax ([syn (lambda (stx) ; change 1: add [...] (syntax 23))]) ; change 2: add syntax (syn (fun 1) 20)))) (lambda (x) (+ x 1)))
Expanding this expression in the model eliminates the inner application of fun, because the syn macro is a constant function. By comparison, expanding expand this expression in Racket with expand results in almost the same kind of tree:
|
|
| ||||||||||||||||||||
|
|
|
> (let ([fun (lambda (x) 65)]) (list (fun 0) (let-syntax ((syn (lambda (stx) (displayln stx) #'23))) (list (syn) (syn 10) (syn 10 20))) (fun 1)))
#<syntax:eval:13:0 (syn)>
#<syntax:eval:13:0 (syn 10)>
#<syntax:eval:13:0 (syn 10 20)>
'(65 (23 23 23) 65)
Stop! Like the model, Racket comes with a function that extracts the immediate element of a syntax node; instead of Syntax-e it is called syntax-e. Use this function in conjunction with displayln to extract some of the tree’s pieces.
Exercise 1. Design syntax-to-list. The function consumes a Racket syntax structure and produces a list of syntax nodes, if the underlying element is a list; otherwise it returns #f. End
Exercise 2. Design syntax-to-datum. The function consumes a Racket syntax node and produces the underlying S-expression. That is, the function traverses the syntax tree and eliminates all syntax structures. End
3.2 Defining Functions at Compile Time and Run Time
The let-syntax simultaneously plays two roles, which are often conflated. First, it creates a compile-time function from the right-hand side: (lambda (stx) #'23). Second, it hooks a name, syn in the example of the preceding section, to this function.
Key to understanding this idea is an appreciation for the distinction between compile time and run time. Every programmer knows the latter; functions defined in a program are called and this function call is evaluated at run time, after the program has been compiled to platform-appropriate code, linked with a run-time library, loaded and started. By contrast, a compile-time function is defined to be known to the compiler.
definition of run-time function |
| ... compile-time function | ||
(define (f x) 5) |
|
| ||
| an abbreviation: | |||
| (define-for-syntax (f x) 5) |
#lang racket (begin-for-syntax (define fun-defined-at-compile-time (lambda (stx) (syntax 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)))
(define-syntax id expr)
(begin-for-syntax (define (fun-defined-at-compile-time stx) (syntax 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))) '(65 (23 23 23) 65)
> (syn 1 2 3 4) 23
3.3 Plain Syntax Macros
> (define-for-syntax (f stx) 5) > (begin-for-syntax (displayln (f 10))) 5
> (f 10)
f: undefined;
cannot reference an identifier before its definition
in module: 'program
> (define-syntax f-bad f) > (f-bad 10)
f-bad: received value from syntax expander was not syntax
received: 5
(begin-for-syntax ; Syntax -> Syntax ; generate code that reports how many arguments @racket[g-macro] is given (define (g stx) (define n (length (syntax->list stx))) (quasisyntax (unsyntax (- n 1)))))
(define-syntax g-macro g) (g-macro) 0
(g-macro a) 1
(g-macro a b) 2
; extract the source location information and generate code from it (define-syntax (i stx) (define l (syntax-line stx)) (define c (syntax-column stx)) (quasisyntax (list "line and column info" (unsyntax l) (unsyntax c))))
> (i-macro 1) '("line and column info" 29 0)
> (i-macro 2) '("line and column info" 30 0)
> (i-macro 3) '("line and column info" 31 0)
(define-syntax (define-hello stx) (define expression (syntax-e stx)) (define variable (second expression)) (define definition (list 'define variable "world, how are you?")) (quasisyntax (unsyntax definition)))
> (define-hello x) > x "world, how are you?"
keyword |
| description |
| a block of compile-time definitions and expressions | |
| a sinhle compile-time definition | |
| a mechanism for expanding the compiler with a function |
short hand |
| full keyword |
| like |
#’ |
|
| quote for constructing code | |
#‘ |
|
| quasiquote for constructing & computing code | |
#, |
|
| unquote for escaping to computations | |
#,@ |
|
| unquote-splicing for escaping to computations |
3.4 Beyond the Model
Let’s take one last look at datum->syntax, because we will run into it again. Its first argument is stx above, the given syntax tree. The function copies properties from stx to the new syntax tree, in particular, it copes over scope information. We will get back to this later.
3.4.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)))))
3.4.2 The Core is Flexible
#lang racket (let ([fun (lambda (x) x)]) (fun (let-synntax ((lambda (lambda (stx) #'23))) (let ([fun (lambda (x) (+ 99 x))]) fun))))
The point of the next couple of chapters is to explain the vast machinery of tools we have to define compile-time functions and macros with notational economy.