21.1 Patterns and templates
A Dylan macro consists of a set of rules. Each rule has two basic parts: a pattern that is matched against a fragment of code, and a template that is substituted for the matched fragment, perhaps including pieces of the original fragment. When a macro is invoked, each rule is tried in order until a matching pattern is found. When a match is found, the macro is replaced by the matching template. If no match can be found, an error occurs.
Dylan macros are recognized by the compiler because they fit one of three possible formats: the function macro, the statement macro, and the defining macro. The macro format determines the overall fragment that is matched against the macro's rules at each macro invocation.
The simplest macro format that the compiler can match is that of a function call. A function macro is invoked in exactly the same way that a function is invoked. The name of the macro is a module variable that can be used anywhere a function call can occur. Typically, it is simply the name followed by a parenthesized list of arguments, but recall that slot-style abbreviations and unary and binary operators are also function calls.
The most important use of function macros is to rearrange or delay evaluation of arguments. The fragment that is matched against the function macro's rules is the phrase that represents a function's arguments. The function macro can then rearrange the function arguments, perhaps adding code. When a macro rearranges its arguments, its action has the effect of delaying the evaluation of the arguments (as opposed to a function call, where the argument expressions are evaluated and then passed to the function).
One simple use of delaying evaluation is to write a functionlike construct similar in spirit to C's ?: operator:
define macro if-else
{ if-else (?test:expression, ?true:expression, ?false:expression) }
=> { if (?test) ?true else ?false end }
end macro if-else;
We could not write if-else as a function, because both the true and false expressions would be evaluated before the function was even called:
? define variable *x* = 0; ? define variable *y* = 0; ? *y* := if-else(*y* == 0, *x* := 1, *x* := -1); 1 ? *y*; 1 ? *x*; 1
If we had defined if-else as a function, *x* would have been -1, rather than 1, because both assignments to *x* would have been evaluated, before if-else was called. When a macro is used, the assignments are just substituted into the template if, which evaluates the first clause only when the condition is true.
Looking at the macro definition of if-else, we can infer basic ideas about macros. A macro is introduced by define macro, followed by the macro name — in this case, if-else. The definition of the macro is a rule that has two parts: a pattern enclosed in braces, {}, that mimics the fragment that it is to match, and a replacement. Macro parameters, called pattern variables, are introduced in the pattern by ?. They match fragments with particular constraints — in this case, :expression. They are delimited by punctuation — in this case, the open and close parentheses, (), and the comma, ,.
The replacement part of the rule, the expansion, is indicated by => and is defined by a template, also enclosed in braces. The template is in the form of a code fragment, where pattern variables are used to substitute in the fragments they matched in the pattern. Note that matching and replacement are language based, so required and optional whitespace is treated exactly as in Dylan. We have used optional whitespace to improve the legibility of the macro definitions presented here.
Most Dylan development environments provide a way to view code after all macros have been expanded. This view can be helpful in debugging macros that you write. For example, showing the expanded view of an expression like
*y* := if-else(*y* == 0, *x* := 1, *x* := -1);;
might yield
*y*:= if (*y* == 0)*x*:= 1 else*x*:= -1 end;
The exact format of the expanded view of the macro depends on the particular development environment. Here, we show the code that comes from the macro template in underlined italic, whereas the fragments matched by the pattern variables and substituted into the template are presented in our conventional code font. Note that the if-else macro we have defined is just syntactic sugar — Dylan's built-in if statement is perfectly sufficient for the job.
Another reason to delay evaluation is to change the value of an argument — for example, to implement an operator similar in spirit to C's ++ and += operators:
define macro inc!
{ inc! (?place:expression, ?by:expression) }
=> { ?place := ?place + ?by; }
{ inc! (?place:expression) }
=> { ?place := ?place + 1; }
end macro inc!;
This macro might be used as follows:
? define variable *x* = 0; ? inc!(*x*, 3); 3 ? *x*; 3 ? inc!(*x*); 4 ? *x*; 4
In this macro, it is important to delay the evaluation of the first argument because we want to be able to assign to the variable or slot it is stored in, rather than simply to manipulate the value of the variable or slot.
The inc! macro demonstrates the use of multiple rules in a macro. They are tried in order until an appropriate match is found. This allows the inc! macro to have two forms. The one-argument form increments the argument by 1. The two-argument form allows the increment amount to be specified.




