source: project/wiki/explicit-renaming-macros @ 37503

Last change on this file since 37503 was 37488, checked in by juergen, 11 months ago
  • Property svnwiki:tags set to macros explicit-renaming
File size: 11.5 KB
Line 
1[[toc:]]
2
3== Mini-tutorial on explicit (and implicit) renaming macros in CHICKEN
4
5The following is an attempt to explain, how to write hygienic macros in
6CHICKEN with explicit renaming. It supposes, that the reader knows, how
7to write other low-level macros like define-macro in CHICKEN-3 and other
8Schemes, or defmacro in Lisp. As you will see, explicit renaming macros
9are even easier to write than those with define-macro. This is because
10there is a brute force method to avoid variable capture.
11
12Let's consider a trivial macro a la CHICKEN-3 without any considerations
13on hygiene and variable capture:
14
15<enscript highligt="scheme">
16(define-macro (swap! x y) ; wrong
17  `(let ((tmp ,x))
18      (set! ,x ,y)
19      (set! ,y tmp)))
20</enscript>
21
22You might be surprised, but the replacement text, that within the
23backquotes, need not be changed in an explicit renaming macro, at least
24in a first step. What has to be changed, is the signature: explicit
25renaming transformers are always procedures of three variables, usually
26called form, rename, compare?, which has to be wrapped into an
27er-macro-transformer call:
28
29<enscript highlight="scheme">
30(define-syntax swap!
31  (er-macro-transformer
32    (lambda (form rename compare?)
33      ...
34      `(let ((tmp ,x))
35         (set! ,x ,y)
36         (set! ,y tmp)))))
37</enscript>
38
39and you have to destructure the form argument, which is the whole
40macro-from, (swap! x y) in the example, to fill in the ellipsis above.
41That is
42
43<enscript highlight="scheme">
44(define-syntax swap! ; wrong
45  (er-macro-transformer
46    (lambda (form rename compare?)
47      (let ((x (cadr form)) (y (caddr form)))
48        `(let ((tmp ,x))
49           (set! ,x ,y)
50           (set! ,y tmp))))))
51</enscript>
52
53In this form, the new macro is the same as our first attempt with
54define-macro. We haven't bothered about variable capture or hygiene at
55all. But this macro already works. You can test it, say with
56
57<enscript highlight="scheme">
58(let ((x 'x) (y 'y))
59  (swap! x y)
60  (list x y))
61</enscript>
62
63and see if it does what it is supposed to do. Of course, it is not
64correct, since if a client uses tmp as one of his or her arguments, the
65macro will crash.
66
67In the classical macro systems, defmacro in Common Lisp or define-macro
68in some Schemes, you now have to think carefully, which macro variables
69are in danger of variable capture and use an uninterned symbol for them,
70tmp in the current example. Worse than that, contrary to Common Lisp,
71Scheme allows to use any name for variables, even keyword names as let,
72set!, ... so that define-macro can never create a hygienic swap! macro.
73Here is, where renaming comes into the play. You don't need gensym, use
74rename instead.  And you needn't bother, which symbol to rename, rename
75all.
76
77What does that mean, "all"? It's easy in the present example, everything
78within the backquote, except those symbols, which are already unquoted.
79But in more complicated examples, you should use expand, to see the
80replacement text of your macro calls.
81
82<enscript highlight="scheme">
83  (pp (expand '(swap! x y)))
84</enscript>
85
86will result in the replacement text
87
88<enscript highlight="scheme">
89(let ((tmp x))
90  (set! x y)
91  (set! y tmp))
92</enscript>
93
94Only x and y are arguments of your swap! call, hence everything else in
95the replacement text should be renamed: namely let, tmp and set!. So a
96hygienic version of swap! would be
97
98<enscript highlight="scheme">
99(define-syntax swap!
100  (er-macro-transformer
101    (lambda (form rename compare?)
102      (let ((x (cadr form)) (y (caddr form)))
103        `(,(rename 'let) ((,(rename 'tmp) ,x))
104           (,(rename 'set!) ,x ,y)
105           (,(rename 'set!) ,y ,(rename 'tmp)))))))
106</enscript>
107
108If you repeat the expand call above, you will get the same replacement
109text, but with numbers suffixed to the renamed symbols, something like
110
111<enscript highlight="scheme">
112(let11 ((tmp12 x))
113  (set!13 x y)
114  (set!13 y tmp12))
115</enscript>
116
117These renamed symbols have the same meaning as the original names
118without suffixes, but serve as aliases which the client cannot use under
119any circumstances.
120
121Note, that the two appearances of tmp and set! are renamed to the same
122alias, the rename operator is referentially transparent. Note also, that
123these renamed symbols are much easier to interpret than gensym'ed ones!
124
125I personally do not like these rename calls within the backquoted
126expression, because you loose the visual similarity between the
127backquoted expression and the resulting replacement text. Therefore I
128prefer this version, which is equivalent
129
130<enscript highlight="scheme">
131(define-syntax swap!
132  (er-macro-transformer
133    (lambda (form rename compare?)
134      (let (
135        (x (cadr form))
136        (y (caddr form))
137        (%tmp (rename 'tmp))
138        (%let (rename 'let))
139        (%set! (rename 'set!))
140        )
141        `(,%let ((,%tmp ,x))
142           (,%set! ,x ,y)
143           (,%set! ,y ,%tmp))))))
144</enscript>
145
146You can think of the two characters ,% in the template as an identity
147operator.
148
149The last version is easy to write, because the macro is small. There are
150only three symbols to be renamed.
151
152But what if you have a giant macro with dozens of symbols to be renamed?
153It's not a problem to prefix them all with ,% in the replacement text,
154but then you have to add a giant let defining the renamed symbols.  And
155this is cumbersome indeed.
156
157Wouldn't it be nice, if this additional let could be added
158automatically?
159
160Well, there is a trick to achieve this. It's based on
161read-macros.  And, I've written a macro named define-er-macro which
162does it as well. But first, here is the trick with a read-macro.
163
164You can add a read-macro with (set-read-syntax! chr proc) just before
165the macro definition and remove it with (set-read-syntax! chr #f) after
166the definition. But if chr were already set to read-syntax before, the
167former call would override and hence destroy it. So a better way is to
168save and restore the whole read-table, which is done with the parameter
169current-read-table, which stores all read-macros.
170
171So here is a version of swap! using this trick.
172
173<enscript highlight="scheme">
174;; save read-table
175(define old-crt (copy-read-table (current-read-table)))
176
177;; define read-macro
178(set-read-syntax! #\%
179                  (lambda (port)
180                    (let ((xpr (read port)))
181                      (if (symbol? xpr)
182                        `(rename ',xpr)
183                        xpr))))
184
185;; define macro
186(define-syntax swap!
187  (er-macro-transformer
188    (lambda (form rename compare?)
189      (let ((x (cadr form))
190            (y (caddr form)))
191        `(,%let ((,%tmp ,x))
192           (,%set! ,x ,y)
193           (,%set! ,y ,%tmp))))))
194
195;; restore read-table
196(current-read-table old-crt)
197</enscript>
198
199You can convince yourself that renaming has taken place without being
200defined explicitly by simply issuing (pp (expand '(swap! x y))) as above.
201
202And here is the much easier solution with define-er-macro from the
203procedural-macros egg.
204
205<enscript highlight="scheme">
206(define-er-macro (swap! form % compare?)
207  (let ((x (cadr form))
208        (y (caddr form)))
209    `(,%let ((,%tmp ,x))
210       (,%set! ,x ,y)
211       (,%set! ,y ,%tmp))))
212</enscript>
213
214Note, that instead of the rename parameter, you must provide a
215rename-prefix, % in our example. The let with the renamed symbols is
216generated automatically, as you can see again with
217(pp (expand '(swap! x y)))
218
219We havn't used the compare? argument in the transformer in this example.
220What's its role?
221
222Well, some macros use special symbols verbatim as additional keywords,
223else and => in cond for example. With compare? it's possible to check,
224if these symbols, which might appear verbatim in a list, have the same
225meaning as their renamed variants (rename 'else), (rename '=>). For
226example, if else appears as the car of a list, lst say, you can code
227something like
228
229<enscript highlight="scheme">
230(if (compare? (car lst) (rename 'else)) ...)
231</enscript>
232
233You see, explicit renaming macros are a bit more verbose than classical
234ones with define-macro. But everybody who is able to write the latter,
235is able to write the former as well. It's even easier!
236
237== Implicit renaming macros
238
239But it can still be improved. Above, I said "You needn't bother which
240symbol to rename, rename all". Well, "all" is something which can
241perfectly be done by a machine. Consequently, since the advent of
242CHICKEN version 4.7, there is another low level macro system, based on
243ir-macro-transformer, which does this "rename all" in the background.
244
245In explicit renaming macros, I had to tell the compiler explicitly, what
246to rename. Every symbol not renamed would break hygiene. In implicit
247renaming macros, the roles are reversed: I have to tell the compiler
248explicitly, which symbol should break hygiene, everything else is
249renamed, whence hygienic. A hygienic swap! macro now looks like this
250
251<enscript highlight="scheme">
252(define-syntax swap!
253  (ir-macro-transformer
254    (lambda (form inject compare?)
255      (let ((x (cadr form)) (y (caddr form)))
256        `(let ((tmp ,x))
257            (set! ,x ,y)
258            (set! ,y tmp))))))
259</enscript>
260
261Note, that the second argument to the transformer is now called inject.
262It's used to "inject" a symbol unrenamed into the replacement text. Note
263further, that there is no need to gensym tmp as in define-macro. And
264last but not least, it's obligatory to wrap the transformer into an
265ir-macro-transformer call: the compiler must know, which low-level
266system to compile, after all.
267
268For implicit renaming macros, the third argument to the transformer is
269now used a bit differently, for example
270
271<enscript highlight="scheme">
272(if (compare? 'else (car lst)) ...)
273</enscript>
274
275== How to automate matters
276
277Up to now, we have always destructured the macro-code, (swap! x y), by
278hand. define-macro did it automatically. Is there a way to do it
279automatically with explicit- and implicit-renaming-macros as well?
280
281Yes, there is. You can use the bind macro from the bindings module.
282It's a version of destructuring-bind of Common Lisp.
283Using it you can replace the let above
284
285<enscript highlight="scheme">
286(let ((x (cadr form)) (y (caddr form))) ...)
287</enscript>
288
289by
290
291<enscript highlight="scheme">
292(import-for-syntax (only bindings bind))
293(bind (_ x y) form ...)
294</enscript>
295
296That isn't that helpful for such a simple form, but what if form is
297deeply nested?  Then your code would be much more readable with bind,
298than with deeply nested lets.
299
300But we still aren't satisfied. Ok, destructuring can be simplyfied with
301bind, but what about renaming/injecting and additional keywords?
302
303Well, the procedural-macros module, will help with solving these
304problems.  For example, we've provided a procedural variant of
305syntax-rules, named macro-rules, which cares for all three arguments of
306the macro-transformer, and -- based on it -- a hygienic version of
307define-macro.
308
309<enscript highlight="scheme">
310(define-macro (swap! x y)
311  `(let ((tmp ,x))
312     (set! ,x ,y)
313     (set! ,y tmp)))
314</enscript>
315
316This is now hygienic!
317
318The following is an example for using maro-rules, a verbose if, whith
319additional keywords.
320
321<enscript highlight="scheme">
322(import-for-syntax (only procedural-macros macro-rules))
323(define-syntax vif
324  (macro-rules (then else)
325    ((_ test (then . xprs) (else . yprs))
326     `(if ,test
327                                (begin ,@xprs)
328                                (begin ,@yprs)))))
329</enscript>
330
331But note, the real power of procedural macros result from the fact, that
332you have complete control over the evaluation time. For example, you can
333use local procedures evaluated at compile time!
334
335For details see
336
337[[/eggref/5/procedural-macros|procedural-macros]]
338
339Procedural macros are really great ...
340
341== Author
342
343[[/users/juergen-lorenz|Juergen Lorenz]]
344
345== Initial version
346
347Jun 09, 2009
348
349== Updated
350
351Nov 26, 2015
352
353
354== Updated again
355
356Nov 01, 2017
357
358== and again for chicken-5
359
360Mar 29, 2019
361
Note: See TracBrowser for help on using the repository browser.