Jon is right. Here’s an explanation why.
Think of `...` as a postfix operator. It repeats what comes before it a certain
number of times. In order for `...` to know how many times to repeat the
previous head template, it looks inside the head template for any attributes
bound at the appropriate ellipsis depth, and it repeats the head template once
for each value of the attributes. The `...` operator essentially creates a
loop, iterating through each value of the attribute.
The value of attributes bound under ellipses are therefore lists. You can see
this for yourself if you use the `attribute` accessor to explicitly get at the
value of an attribute matched under an ellipsis:
> (syntax-parse #'(a b c)
[(x:id ...)
(attribute x)])
'(#<syntax:2:17 a>
#<syntax:2:19 b>
#<syntax:2:21 c>)
But what happens to the value of an attribute when it is completely
unspecified, since it has been marked `~optional`? If the `~optional` wraps the
whole sequence, such that the ellipsis is inside the `~optional` pattern, then
the attribute is not a list at all, but `#f`:
> (syntax-parse #'()
[({~optional (x:id ...)})
(attribute x)])
#f
This causes problems. If we were to write #'(x ...) when `x` is bound to `#f`,
then the template will raise an error, since `x` isn’t a list, and therefore
the ellipses don’t know how many times to repeat the preceding template.
What you tried to do is silence that error by wrapping the offending template
with `~?`. This is a natural thing to try, but it doesn’t work. Why? Well, it’s
true that {~? x} turns into {~@} when `x` is `#f`, but this does not matter,
since you essentially wrote ({~? x} ...). This means that the `x` under the
ellipsis doesn’t refer to the attribute `x` as a whole, but instead refers to
each *element* of `x`, since `...` creates a loop. So the template attempts to
iterate through the values of (attribute x), but it finds that value isn’t a
list at all, gets confused, and explodes.
Jon’s fix changes this. It moves the looping *inside* the `~?`, which means
that `~?` is now looking at `x` as a whole (not each element of `x`), and just
skips the loop altogether, avoiding the error. It’s morally the difference
between this code:
(for/list ([x (in-list (attribute x))])
(if x x #'{~@}))
and this code:
(if (attribute x)
(for/list ([x (in-list (attribute x))])
x)
#'{~@})
---
A secondary question: is the template #'({~? x} ...) ever useful? And the
answer is: YES! It just does something different.
Since #'({~? x} ...) iterates through the values of `x` before checking for
`#f`-ness, then it is useful when `x` itself is never `#f`, but elements of it
may be. This can appear when parsing, say, a list of pairs, where the second
element of each pair is optional:
> (define parse-pairs
(syntax-parser
[([x:id {~optional n:nat}] ...)
#'([x ...]
[{~? n} ...])]))
> (parse-pairs #'([a 1] [b] [c 3]))
#<syntax ((a b c) (1 3))>
> (parse-pairs #'([a] [b] [c]))
#<syntax ((a b c) ())>
Note that when there are no numbers in the input, the output list is still
present but is simply empty, while when some but not all numbers are provided,
the missing numbers are simply skipped in the output.
---
One final point: you may think to yourself “all of this is confusing and
procedural, why should I have to think about attributes?” While I think
understanding what’s going on internally can be helpful, it isn’t strictly
necessary. There’s actually a declarative intuition to guide whether you should
write #'({~? {~@ x ...}}) or #'({~? x} ...). This intuition is as follows.
There is a dualism in `syntax-parse`’s pattern language and in the `syntax`
form’s template language. For example:
- Writing `x` in a pattern, where `x` is an identifier, matches a term, and
writing `x` in a template constructs the same term.
- Writing (a . b) in a pattern matches a pair, and writing (a . b) in a
template constructs a pair.
- Writing `p ...` in a pattern matches zero or more occurrences of the
pattern `p`, and writing `t ...` in a template that contains variables bound in
`p` constructs the same number of occurrences in the template.
To put things another way:
- Variables in patterns correspond to variables in templates.
- (a . b) in patterns corresponds to (a . b) in templates.
- `...` in patterns corresponds to `...` in templates.
This might seem obvious, but it turns out that `~?` and `~@` have cousins in
the pattern language, too. They are just less obvious, because their cousins
have different names:
- `~optional` in patterns corresponds to `~?` in templates.
- `~seq` in patterns corresponds to `~@` in templates.
(I would have liked `~?` and `~@` to be named `~optional` and `~seq` to make
this duality clearer, but sadly, since `~?` and `~@` were added recently,
reusing existing names would make them backwards incompatible with older code
that expanded to `syntax-parse` patterns.)
Since `~optional` corresponds to `~?`, this means that if you put the ellipsis
inside the `~optional`, it should go inside the `~?`. Likewise, if you put the
ellipsis outside the `~optional`, it should go outside the `~?`. To illustrate,
in your code, you wrote the pattern
{~optional (rule:expr ...)}
with the ellipsis inside the `~optional`, so the ellipsis should go inside the
`~?` in the template:
{~? {~@ rule ...}}
Correspondingly, when I wrote the pattern
([x:id {~optional n:nat}] ...)
I put the ellipsis outside the `~optional`, so it should go outside the `~?`:
[{~? n} ...]
Hope this helps,
Alexis
P.S. You may wonder where the `~@` came from in your example, since I said it
corresponds to `~seq`, but your pattern has no `~seq` in it. This is true, but
all list patterns in syntax/parse effectively have a sort of “implicit `~seq`
around them. Your pattern could also have been written like
{~optional ({~seq rule:expr ...})}
though of course that would be silly, since the list pattern already provides
the necessary grouping. However, in your template, you are not expanding to a
list but instead splicing into a surrounding list, so you need explicit
grouping with `~@`. However, if you wished to omit the entire (list ....)
expression in the expansion altogether, then you could write simply
{~? (list x ...)}
since the list template, just like your list pattern, provides the grouping
implicitly. You could even write
{~? (list x ...) (list)}
to avoid the `~@` but have the same behavior as your pattern. Which one you
prefer is just personal preference.
P.P.S. Sorry for all the curly braces. I made a habit of writing
pattern/template special forms in curly braces at some point to visually
disambiguate them from list patterns/templates, and now the muscle memory is
stuck. I don’t think anyone else does this; I’m just weird. They don’t do
anything differently from normal parens.
> On Feb 15, 2019, at 23:44, Jon Zeppieri <[email protected]> wrote:
>
>
> On Fri, Feb 15, 2019 at 11:50 PM David Storrs <[email protected]> wrote:
>
> #lang racket
> (require (for-syntax racket/syntax syntax/parse))
>
> (define-syntax (struct++ stx)
> (syntax-parse stx
> [(_ name:id (field:id ...) (~optional (rule:expr ...)) opt ...)
> #'(begin (struct name (field ...) opt ...)
> (list (~? rule) ... ))]))
>
>
> I think this does what you want:
> (list (~? (~@ rule ...)))
--
You received this message because you are subscribed to the Google Groups
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.