Thank you both -- Jon for the code and Alexis for the thorough description.

Alexis, this was exactly what I was looking for.  I've been frustrated
with macros because I kept trying and failing to figure out a mental
model of what the heck was going on under the hood.  I had some of the
basics, but anything more sophisticated I was basically guessing and
cargo-culting.

I think it would help a lot of future people if your explanation got
pasted verbatim into the docs.

On Sat, Feb 16, 2019 at 1:53 AM Alexis King <lexi.lam...@gmail.com> wrote:
>
> 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