23.11.2014 Views

2006 Scheme and Functional Programming Papers, University of

2006 Scheme and Functional Programming Papers, University of

2006 Scheme and Functional Programming Papers, University of

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

2. Explaining Macros<br />

Macro expansion takes place during parsing. As the parser traverses<br />

the concrete syntax, 1 it creates abstract syntax nodes for primitive<br />

syntactic forms, but stops when it recognizes the use <strong>of</strong> a macro.<br />

At that point, it h<strong>and</strong>s over the (sub)phrases to the macro, which,<br />

roughly speaking, acts as a rewriting rule.<br />

In Lisp <strong>and</strong> in some <strong>Scheme</strong> implementations, a macro is expressed<br />

as a plain function; in R 5 RS <strong>Scheme</strong> [19], macros are<br />

expressed in a sub-language <strong>of</strong> rewriting rules based on patterns.<br />

Also in Lisp, concrete syntax are just S-expressions; Lisp macro<br />

programming is thus typically first-order functional programming 2<br />

over pairs <strong>and</strong> symbols. The most widely used <strong>Scheme</strong> implementations,<br />

however, represent concrete syntax with structures that<br />

carry additional information: lexical binding information, original<br />

source location, code security annotations, <strong>and</strong> others. <strong>Scheme</strong><br />

macro programming is therefore functional programming with a<br />

rich algebraic datatype.<br />

Given appropriate inputs, a Lisp macro can go wrong in two<br />

ways. First, the macro transformer itself may raise a run-time exception.<br />

This case is naturally in the domain <strong>of</strong> run-time debuggers;<br />

after all, it is just a matter <strong>of</strong> traditional functional programming.<br />

Second, a Lisp macro may create a new term that misuses a syntactic<br />

form, which might be a primitive form or another macro. This<br />

kind <strong>of</strong> error is not detected when the macro is executing, but only<br />

afterwards when the parser-exp<strong>and</strong>er reaches the misused term.<br />

Modern <strong>Scheme</strong> macros might go wrong in yet another way.<br />

The additional information in a syntax object interacts with other<br />

macros <strong>and</strong> primitive special forms. For example, macro-introduced<br />

identifiers carry a mark that identifies the point <strong>of</strong> their introduction<br />

<strong>and</strong> binding forms interpret identifiers with different marks<br />

as distinct names. <strong>Scheme</strong> macros must not only compute a correct<br />

replacement tree but also equip it with the proper additional<br />

properties.<br />

Even in Lisp, which has supported macros for almost 50<br />

years now, macros have always had impoverished debugging environments.<br />

A typical Lisp environment supports just two procedures/tools<br />

for this purpose: exp<strong>and</strong> <strong>and</strong> exp<strong>and</strong>-once (or<br />

macroexp<strong>and</strong> <strong>and</strong> macroexp<strong>and</strong>-1 [28]). All <strong>Scheme</strong> implementations<br />

with macros have adapted these procedures.<br />

When applied to a term, exp<strong>and</strong> completely parses <strong>and</strong> exp<strong>and</strong>s<br />

it; in particular, it does not show the intermediate steps <strong>of</strong> the rewriting<br />

process. As a result, exp<strong>and</strong> distracts the programmer with too<br />

many irrelevant details. For example, <strong>Scheme</strong> has three conditional<br />

expressions: if, cond, <strong>and</strong> case. Most <strong>Scheme</strong> implementations<br />

implement only if as a primitive form <strong>and</strong> define cond <strong>and</strong> case<br />

as macros. Whether or not a special form is a primitive form or a<br />

macro is irrelevant to a programmer except that macro expansion<br />

reveals the difference. It is thus impossible to study the effects <strong>of</strong><br />

a single macro or a group <strong>of</strong> related macros in an expansion, because<br />

exp<strong>and</strong> processes all macros <strong>and</strong> displays the entire abstract<br />

syntax tree.<br />

The task <strong>of</strong> showing individual expansion steps is left to the second<br />

tool: exp<strong>and</strong>-once. It consumes a macro application, applies<br />

the matching macro transformer, <strong>and</strong> returns the result. In particular,<br />

when an error shows up due to complex macro interactions,<br />

it becomes difficult to use exp<strong>and</strong>-once easily because the <strong>of</strong>fending<br />

or interesting pieces are <strong>of</strong>ten hidden under a large pile <strong>of</strong><br />

syntax. Worse, iterated calls to exp<strong>and</strong>-once lose information between<br />

expansion steps, because lexical scope <strong>and</strong> other information<br />

depends on the context <strong>of</strong> the expansion call. This problem renders<br />

exp<strong>and</strong>-once unfit for serious macro debugging.<br />

1 We consider the result <strong>of</strong> (read) as syntax.<br />

2 Both Lisp <strong>and</strong> <strong>Scheme</strong> macro programmers occasionally use side-effects<br />

but aside from gensym it is rare.<br />

Implementing a better set <strong>of</strong> debugging tools than exp<strong>and</strong> <strong>and</strong><br />

exp<strong>and</strong>-once is surprisingly difficult. It is apparently impossible<br />

to adapt the techniques known from run-time debugging. For example,<br />

any attempt to pre-process the syntax <strong>and</strong> attach debugging information<br />

or insert debugging statements fails for two reasons: first,<br />

until parsing <strong>and</strong> macro expansion happens, the syntactic structure<br />

<strong>of</strong> the tree is unknown; second, because macros inspect their arguments,<br />

annotations or modifications are likely to change the result<br />

<strong>of</strong> the expansion process [31].<br />

While these reasons explain the dearth <strong>of</strong> macro debugging<br />

tools <strong>and</strong> steppers, they don’t reduce the need for them. What we<br />

present in this paper is a mechanism for instrumenting the macro<br />

exp<strong>and</strong>er <strong>and</strong> for displaying the expansion events <strong>and</strong> intermediate<br />

stages in a useful manner. Eventually we also hope to derive a wellfounded<br />

model <strong>of</strong> macros from this work.<br />

3. The Macro Debugger at Work<br />

The core <strong>of</strong> our macro debugging tool is a stepper for macro<br />

expansion in PLT <strong>Scheme</strong>. Our macro debugger shows the macro<br />

expansion process as a reduction sequence, where the redexes are<br />

macro applications <strong>and</strong> the contexts are primitive syntactic forms,<br />

i.e., nodes in the final abstract syntax tree. The debugger also<br />

includes a syntax display <strong>and</strong> browser that helps programmers<br />

visualize properties <strong>of</strong> syntax objects.<br />

The macro stepper is parameterized over a set <strong>of</strong> “opaque”<br />

syntactic forms. Typically this set includes those macros imported<br />

from libraries or other modules. The macro programmers are in<br />

charge, however, <strong>and</strong> may designate macros as opaque as needed.<br />

When the debugger encounters an opaque macro, it deals with the<br />

macro as if it were a primitive syntactic form. That is, it creates an<br />

abstract syntax node that hides the actual expansion <strong>of</strong> the macro.<br />

Naturally, it does show the expansion <strong>of</strong> the subexpressions <strong>of</strong> the<br />

macro form. The parameterization <strong>of</strong> primitive forms thus allows<br />

programmers to work at the abstraction level <strong>of</strong> their choice. We<br />

have found this feature <strong>of</strong> the debugger critical for dealing with<br />

any nontrivial programs.<br />

The rest <strong>of</strong> this section is a brief illustrative demonstration <strong>of</strong> the<br />

debugger. We have picked three problems with macros from recent<br />

discussions on PLT <strong>Scheme</strong> mailing lists, though we have distilled<br />

them into a shape that is suitably simple for a technical paper.<br />

3.1 Plain Macros<br />

For our first example we consider a debugging scenario where the<br />

macro writer gets the form <strong>of</strong> the result wrong. Here are three different<br />

versions <strong>of</strong> a sample macro that consumes a list <strong>of</strong> identifiers<br />

<strong>and</strong> produces a list <strong>of</strong> trivial definitions for these identifiers:<br />

1. in Lisp, the macro writer uses plain list-processing functions to<br />

create the result term:<br />

(define-macro (def-false . names)<br />

(map (lambda (a) ‘(define ,a #f)) names))<br />

2. in R 5 RS the same macro is expressed with a rewriting rule<br />

notation like this:<br />

(define-syntax def-false<br />

(syntax-rules ()<br />

[(def-false a ...) ((define a #f) ...)]))<br />

3. in major alternative <strong>Scheme</strong> macro systems, the rule specification<br />

is slightly different:<br />

(define-syntax (def-false stx)<br />

(syntax-case stx ()<br />

[(_ a ...) (syntax ((define a #f) ...))]))<br />

16 <strong>Scheme</strong> <strong>and</strong> <strong>Functional</strong> <strong>Programming</strong>, <strong>2006</strong>

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!