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

Last change on this file since 34845 was 34845, checked in by juergen, 2 years ago

explicit-renaming-macros tutorial updated

  • 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
203basic-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 basic-macros 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(bind (_ x y) form ...)
293</enscript>
294
295That isn't that helpful for such a simple form, but what if form is
296deeply nested?  Then your code would be much more readable with bind,
297than with deeply nested lets.
298
299But we still aren't satisfied. Ok, destructuring can be simplyfied with
300bind, but what about renaming/injecting and additional keywords?
301
302Well, the procedural-macros module, which is written on top of
303basic-macros, will help with solving these problems.  For example, we've
304provided a procedural variant of syntax-rules, named macro-rules, which
305cares for all three arguments of the macro-transformer, and -- based on
306it -- a hygienic version of define-macro.
307
308<enscript highlight="scheme">
309(define-macro (swap! x y)
310  `(let ((tmp ,x))
311     (set! ,x ,y)
312     (set! ,y tmp)))
313</enscript>
314
315This is now hygienic!
316
317The following is an example for using maro-rules, a verbose if, whith
318additional keywords.
319
320<enscript highlight="scheme">
321(define-syntax vif
322  (macro-rules (then else)
323    ((_ test (then . xprs) (else . yprs))
324     `(if ,test
325                                (begin ,@xprs)
326                                (begin ,@yprs)))))
327</enscript>
328
329But note, the real power of procedural macros result from the fact, that
330you have complete control over the evaluation time. For example, you can
331use local procedures evaluated at compile time!
332
333For details see
334
335[[/eggref/4/basic-macros|basic-macros]]
336[[/eggref/4/procedural-macros|procedural-macros]]
337
338Procedural macros are really great ...
339
340== Author
341
342[[/users/juergen-lorenz|Juergen Lorenz]]
343
344== Initial version
345
346Jun 09, 2009
347
348== Updated
349
350Nov 26, 2015
351
352
353== Updated again
354
355Nov 01, 2017
Note: See TracBrowser for help on using the repository browser.