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 <zeppi...@gmail.com> wrote:
> 
> 
> On Fri, Feb 15, 2019 at 11:50 PM David Storrs <david.sto...@gmail.com> 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 racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to