1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
|
# Better syntax-rules?
Yesterday, someone on IRC asked for help in improving the following
syntax-rules (s-r) macro:
```scheme
(define-syntax alist-let*
(syntax-rules ()
;; uses subpattern to avoid fender
;; alist-expr is evaluated only once
((_ alist-expr ((key alias) ...) body body* ...)
(let ((alist alist-expr))
(let ((alias (assq-ref alist 'key)) ...)
body body* ...)))
((_ alist-expr (key ...) body body* ...)
(let ((alist alist-expr))
(let ((key (assq-ref alist 'key)) ...)
body body* ...)))
))
;; Example uses:
(define alist '((foo . 1) (bar . 2)))
(alist-let alist (foo bar)
(+ foo bar)) ;=> 3
(alist-let alist ((foo x) (bar y))
(+ x y)) ;=> 3
;; Problem: Can't mix plain key with (key alias) forms:
(alist-let alist ((foo x) bar)
(+ x bar)) ;ERROR
```
How do we make it accept a mix of plain keys and `(key alias)` pairs?
Oh boy, it's more difficult than you may think if you're new to s-r
macros. Basically, there's no "obvious" solution, and all we have is
various hacks we can apply.
Let's look at two fairly straightforward hacks, and their problems.
## Option 1
```scheme
;; Solution 1: Internal helper patterns using a dummy constant.
(define-syntax alist-let*
(syntax-rules ()
((_ "1" alist ((key alias) rest ...) body body* ...)
(let ((alias (assq-ref alist 'key)))
(alist-let* "1" alist (rest ...) body body* ...)))
((_ "1" alist (key rest ...) body body* ...)
(let ((key (assq-ref alist 'key)))
(alist-let* "1" alist (rest ...) body body* ...)))
((_ "1" alist () body body* ...)
(begin body body* ...))
;; dispatch, ensuring alist-expr only eval'd once
((_ <alist> <bindings> <body> <body*> ...)
(let ((alist <alist>))
(alist-let* "1" alist <bindings> <body> <body*> ...)))
))
```
(I've switched to my `<foo>` notation for pattern variables in the
"dispatcher" part. Don't let it distract you. I strongly endorse
that convention for s-r pattern variables, to make it clear that
they're like "empty slots" where *any* expression can match, but
that's a topic for another day.)
What the solution above does, is "dispatch" actual uses of the macro,
which obviously won't have the string literal `"1"` in first position,
onto internal sub-macros, which can call each other recursively, so
each layer only handles either a stand-alone `key` or a `(key alias)`
couple.
There's some nuances to this implementation. First, if you're not
familiar with s-r macros, you may mistakenly worry that this solution
could mask a programmer error: What if we accidentally call the macro
with a variable bound to the string "1"? Would this lead to a very
annoying bug that's hard to find? No; remember that syntax-rules
patterns match *unevaluated* operands, so the internal sub-patterns
are only triggered by the appearance of a literal string constant of
`"1"` in the first position; a mistake that would be very apparent in
code you're reading, and is extremely unlikely to occur by accident.
As for a real pitfall of this implementation: The dispatcher pattern
*must* be in the final position; otherwise it will actually catch our
recursive calls starting with `"1"` and bind that string literal to
the `alist` pattern variable! (Kind of the "reverse" of the fake
problem described in the previous paragraph, in a sense?) If the
dispatcher pattern is in the first position, it will keep calling
itself with an increasing number of `"1"`s at the start, in an
infinite loop, until you forcibly stop it or it crashes.
As a side note, this brings me to a general s-r pitfall, that applies
to the original implementation as well in this case: Since patterns
are matched top to bottom, a simple `key` pattern variable *could*
actually match the form `(key alias)`, so you have to make sure that
the pattern for matching those key-alias couples comes before the one
matching plain keys.
Oh, and by the way, if you're questioning whether we even need those
internal helper patterns at all: Yes, it's the only way to ensure the
initial `<alist>` expression is only evaluated once, in an outermost
`let` wrapping everything.
Let's summarize the issues we've faced:
1. It's easy to forget that pattern variables can match arbitrary
expressions, not just identifiers, and there's no way to say it
should only match identifiers.
2. When an arbitrary expression is matched by the pattern variable,
using it means repeating that expression every time, unless you
explicitly use `let` to take care of that, which may require
dispatching to another pattern immediately if you wanted to use
recursive patterns.
3. You may accidentally put a more generic pattern first, causing it
to match an input that was meant to be matched by a subsequent
pattern with more deeper destructuring.
It may be interesting trying to solve 3 by specifying some way of
measuring the "specificity" of a pattern, and saying that those with
the highest specificity match first, but that may prove difficult.
Besides, solving 1 would basically solve 3 anyway.
Racket has syntax-parse, which solves the first problem through an
incredibly sophisticated specification of "syntax patterns" that take
the place of the humble generic pattern variable of syntax-rules.
It's cool and all, but the charm of s-r is the simplicity. Can't we
use some of the ideas of syntax-parse patterns and add them to s-r?
In Racket, there's the concept of "syntax classes," and a pattern can
be a variable with `:syntax-class-id` appended to its name, which is
how you make it only match inputs of that syntax class, such as for
example, only identifiers. Trying to find out what syntax class ids
are supported may send you down a rabbit hole of how you can actually
define your own syntax classes, but that just seems to be a weak spot
of the Racket online documentation; looking a bit closer, you should
find the list of built-in classes that are supported. They are just
called "library" syntax classes for some reason:
[Library Syntax Classes and Literal Sets -- Racket Documentation](https://docs.racket-lang.org/syntax/Library_Syntax_Classes_and_Literal_Sets.html)
It would be great if there were classes for atoms (anything that's not
a list) and lists, though; then we could do this:
```scheme
(define-syntax alist-let*
(syntax-rules ()
((_ <alist>:list bindings body body* ...)
(let ((alist <alist>))
(alist-let* alist bindings body body* ...)))
((_ alist (key:id ...) body body* ...)
(let ((key (assq-ref alist 'key)) ...)
body body* ...))
((_ alist ((key:atom alias:id) ...) body body* ...)
(let ((alias (assq-ref alist 'key)) ...)
body body* ...))
))
```
(The key could also be a non-symbol immediate value, like a fixnum,
boolean, etc.; anything that `assq-ref` can compare via `eq?`. One
could also just not quote the key, and instead let it be an arbitrary
expression, which would probably make for a more useful macro, but
that's a different topic.)
Isn't that really neat? But let's go one step further. I believe
this strategy of binding an expression via `let` to ensure it's only
evaluated once is probably so common that it warrants a shortcut:
```scheme
(define-syntax alist-let*
(syntax-rules ()
((_ alist:bind (key:id ...) body body* ...)
(let ((key (assq-ref alist 'key)) ...)
body body* ...))
((_ alist:bind ((key:atom alias:id) ...) body body* ...)
(let ((alias (assq-ref alist 'key)) ...)
body body* ...))
))
```
The idea here is: All pattern variables marked with `:bind` are first
collected, and if there is at least one that is not an identifier,
then the whole template (the part that produces the output of the s-r
macro) is wrapped in a `let` which binds those expressions to the name
of the pattern variable, and uses of that pattern variable within the
template refer to that binding.
I'm not entirely sure yet if this is an ingenious idea, or a hacky fix
for just one arbitrary issue you can face while using syntax-rules,
but I suspect it's a common enough pattern to make it desirable.
## Option 2
I said there were various hacks to solve the original problem; here's
the second variant. It's actually almost the same thing, but we put
the helper patterns into a separate macro.
```scheme
;; Solution 2: Separate helper macro
(define-syntax alist-let*
(syntax-rules ()
;; dispatch, ensuring alist-expr only eval'd once
((_ <alist> <bindings> <body> <body*> ...)
(let ((alist <alist>))
(%alist-let-helper alist <bindings> <body> <body*> ...)))
))
(define-syntax %alist-let-helper
(syntax-rules ()
;; basically do here what the internal helpers did in solution 1,
;; but without the need for the "1" string literal hack
))
```
That's cleaner in terms of the patterns we have to write, but we had
to define a second top-level macro, which feels wrong. It should be
properly encapsulated as part of the first.
This is where another improvement to s-r could come in handy, and
that's not making it evaluate to a syntax transformer (i.e., lambda)
directly, but rather making it more like syntax-case in that regard.
However, the additional lambda wrapping always really annoyed me, so
the following syntax may be desirable.
```scheme
(define-syntax (alist-let* . s)
(define-syntax (helper . s)
(syntax-rules s ()
((alist ((key alias) rest ...) body body* ...)
(let ((alias (assq-ref alist 'key)))
(alist-let* "1" alist (rest ...) body body* ...)))
((alist (key rest ...) body body* ...)
(let ((key (assq-ref alist 'key)))
(alist-let* "1" alist (rest ...) body body* ...)))
((alist () body body* ...)
(begin body body* ...))
))
(syntax-rules s ()
((<alist> <bindings> <body> <body*> ...)
(let ((alist <alist>))
(helper alist <bindings> <body> <body*> ...)))))
```
That looks a bit confusing at first sight, but we can actually do
something a lot better now, since we already get one stand-alone
pattern at the start, which fits our intention perfectly here:
```scheme
(define-syntax (alist-let* <alist> <bindings> <body> <body*> ...)
(define-syntax (helper . s)
(syntax-rules s ()
((alist ((key alias) rest ...) body body* ...)
(let ((alias (assq-ref alist 'key)))
(alist-let* "1" alist (rest ...) body body* ...)))
((alist (key rest ...) body body* ...)
(let ((key (assq-ref alist 'key)))
(alist-let* "1" alist (rest ...) body body* ...)))
((alist () body body* ...)
(begin body body* ...))
))
#'(let ((alist <alist>))
(helper alist <bindings> <body> <body*> ...)))
```
To be honest, I don't like this solution nearly as much as the first,
and I now realize that there wouldn't be much point in keeping s-r if
it's going to be so close to syntax-case. (The only difference, at
this point, would be that s-r implicitly puts `#'` in front of the
templates. That's literally all it would do, if I'm not mistaken.)
## Or just implement syntax-parse?
Racket can actually give you the implicit lambda when you want it, by
offering `syntax-parser` as an alternative to `syntax-parse`:
```scheme
;; The following two are equivalent.
(define-syntax foo
(lambda (s)
(syntax-parse s ...)))
(define-syntax foo
(syntax-parser ...))
```
(At least, I'm pretty sure that's how it's supposed to work; the docs
just bind the result of `syntax-parser` to an identifier via `define`
and call it as a procedure to showcase it, for whatever reason.)
Yes, syntax-parse is a lot more complex than syntax-rules, but to be
honest it seems mainly the fault of the documentation that it doesn't
showcase the simplest ways of using it, which look essentially the
same as using syntax-rules, so it's not clear why s-r should stay if
you have syntax-parse.
Maybe I would just make one change, which is to allow the following
syntax and thus make the additional `syntax-parser` unnecessary:
```scheme
(define-syntax (foo s)
(syntax-parse s ...))
```
Note that this is different from my previous idea of making the first
operand to `define-syntax` a pattern. The only thing I don't like
about this variant is that there will never be more than one argument,
but maybe that's fine?
In any case, I guess the only innovation I came up with here is the
special `:bind` syntax class id, assuming there isn't already a
similar thing in Racket or elsewhere.
Oh and this made me realize I should add `foo:bar` as reader syntax to
Zisp, turning it into `(#COLON foo . bar)` or such.
|