On this page:
3.1 What Does this Look Like in Real Racket?
3.2 Defining Functions at Compile Time and Run Time
3.3 Plain Syntax Macros
3.4 Beyond the Model
3.4.1 Scope
3.4.2 The Core is Flexible
7.8.0.8

3 Onward to Racket

Goals

from the model to Racket

steps beyond the model

3.1 What Does this Look Like in Real Racket?

The model is mostly accurate for simple Racket macros. Here is a one of the example from the preceding chapter expressed in 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)))
Unlike in the model, the let-syntax functions must construct syntax explicitly. change model?

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:

> (define racket-example
  
    `((lambda (fun)
      (fun
        (let-syntax
           ([syn (lambda (stx)
                   (,'syntax 23))])
          (syn (fun 1) 20))))
      (lambda (x) (+ x 1))))

   

> (define model-example
   (string-append
    "((lambda (fun)"
    "  (fun"
    "    (let-syntax"
    "       (syn (lambda (stx)"
    "              23))"
    "      (syn (fun 1) 20))))"
    "(lambda (x) (+ x 1)))"))

> (pretty-print
    (syntax->datum
      (expand racket-example)))

'(#%app

  (lambda (fun)

    (#%app

     fun

     (let-values ()

       (let-values () '23))))

  (lambda (x) (#%app + x '1)))

   

> (pretty-print
    (model:Syntax->datum
      (model:expand model-example)))

'(app

  (lambda fun (app fun 23))

  (lambda x (+ x 1)))

The differences can easily be explained. While Racket introduces let-values for local blocks, the model omits these two. Also, the model treats + as a primitive operation, whereas in Racket it is just an ordinary function that is applied like any other function.

Let’s experiment some more with this, but instead of left-left-lambda, we use let: This hash-quote should be a syntax.
> (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)

No matter how many arguments we hand over to syn, it just works fine. Remember that a syntax macro really gets the entire syn-labeled tree. The example adds (displayln stx) to the syn function ,to validate this point.

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.

In Racket, it takes little to define a function at these two different phases:

definition of run-time function

   

... compile-time function

(define (f x) 5)

   

(begin-for-syntax
  (define (f x) 5))

   

an abbreviation:

   

(define-for-syntax (f x) 5)

With these tools, we can now separate Racket’s let-syntax into its two pieces:
#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)))
Because fun-defined-at-compile-time is hooked into the compiler, it is expected to produce a syntax node.

Racket also supports a module-level mechanism for extending the compiler with compile-time defined functions:

(define-syntax id expr)

With define-syntax, we can designate a compile-time function as applying to an entire module:
(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

A function defined with begin-for-syntax can be used only while the compiler transforms the program into executable code:
> (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

As the last interaction shows, using such a function at run time fails with an error message. It also makes no sense to use a for-syntax function as a macro if it doesn’t produce syntax:
> (define-syntax f-bad f)
> (f-bad 10)

f-bad: received value from syntax expander was not syntax

  received: 5

By now, you know that syntax-e delivers the element inside of a syntax node, and this element is a list when the compile time function is used as a macro. Hence we can, for example, have a macro compute the length of a syntax’s underlying list and generate code that reports the number of arguments given or trees contained with stx:
(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)))))
The function uses two new facilities to generate this code: quasisyntax and unsyntax, which roughly correspond to quasiquote and unquote. The former sets up a syntax node but allows sub-terms to be compute via escapes into plain Racket; here the escape is just a simple expression.

Let’s use g as a macro so we can see results easily:
(define-syntax g-macro g)
(g-macro)

0

(g-macro a)

1

(g-macro a b)

2

And indeed, using g-macro generates code that reports the number of sub-terms given.

Racket’s syntax nodes come with additional properties, and a macro can access those. For example, it can retrieve the line or column where the node shows up or its column:
; 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))))
Note how this define-syntax definition introduces the function for transforming syntax directly.

We use i-macro like any other macro:
> (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)

The line numbers are relative to the beginning of this chapter, and both line are column counts start at 0.

Of course, the real purpose of macros is to generate new syntax trees that compute something interesting. Let’s use Racket’s list to generate such a syntax tree:
(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)))
The transformer function extracts the identifier from the second position of the given syntax node and creates a list from 'define, the identifier, and a string. This list looks like a representation of a definition, and quasisyntax turns it into code.

Let’s use it:
> (define-hello x)
> x

"world, how are you?"

And this is a “hello world” program in the world of Racket macros.

We close this section with a summary of keywords and their descriptions

keyword

   

description

begin-for-syntax

   

a block of compile-time definitions and expressions

define-for-syntax

   

a sinhle compile-time definition

define-syntax

   

a mechanism for expanding the compiler with a function

plus some abbreviations for the syntax constructors with long names:

short hand

   

full keyword

   

like

#

   

syntax

   

quote for constructing code

#

   

quasisyntax

   

quasiquote for constructing & computing code

#,

   

unsyntax

   

unquote for escaping to computations

#,@

   

unsyntax-splicing

   

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

What the model does not show is that an expander also copes with lexical scope properly. It is perfectly acceptable to use the same name for compile-time and run-time functions:
#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)))
A compile time function can also generated code that refers to a run-time function of the same name:
#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

Finally, here is a glance of what Racketeers, can do too. You don’t like lambda, well, re-define it for your purposes:
#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.