john.r.rose at oracle.com
Fri Mar 5 23:08:18 UTC 2021
Guy, I think you are right that guards have a natural home
on the right-hand edge of patterns (whether the patterns
are simple P or composite P&Q, etc).
And because patterns can nest, this “right edge” can occur
placed inside a sub-pattern, which lets us smuggle guards
anywhere we like, with a little care: P&(var x when g(x))&Q.
If we double down on this position (which I think is tenable),
then we have another opportunity: The right edge of a
pattern case label and the right edge of an instanceof pattern
are both places where “g(x) && h(x)” is a natural place to look
for more conditional logic.
So I suggest there is a privileged position for guard sugar
that looks like “stuff that follows instanceof”. That is:
switch … case P && g(x):
…instanceof P && g(x)
I.e, let’s spell “when” as “&&”. Then the right edge of
a pattern, wherever it may roam, will be the place your
eye will look for guards, in the form of immedately
following && G sequences. This is not just consistency
for consistency’s sake, I think, but a real gain in
recognizability of patterns. (It’s also a sneaky hack,
since the “&&” doesn’t show up in the pattern grammar,
but rather appears twice, once in the regular expression
grammar, and once in *some* of the grammatical
places where a pattern can occur. But it is a
harmless, even useful sneak.)
Where does this leave the single “&” operator? Where
it always was: It can be a feature added in the future.
In fact, “P&Q” would be sugar for “var t2 && t2 instanceof P
&& t2 instanceof Q”. Perhaps && is the right primitive,
as well as an easy-to-recognize syntax.
On Mar 5, 2021, at 2:12 PM, Guy Steele <guy.steele at oracle.com> wrote:
> Thanks for this summary, Brian. But there is just one place where the argument involves a perhaps unnecessary overcommitment. See below.
>> On Mar 5, 2021, at 2:14 PM, Brian Goetz <brian.goetz at oracle.com> wrote:
>> ... Some sort of guard construct that is usable in switch is a forced move.
>> #### Expressing guards in switch
>> There are several ways to envision guards:
>> - As patterns that refine other patterns (e.g., a "true" pattern)
> A guard construct need not itself be a pattern. Rather, it can be viewed as a map from patterns to patterns. Indeed, they are formulated in exactly that way in Gavin’s BNF in JEP JDK-8213076 "Pattern Matching for switch”: a guard is not a pattern, but can only appear within a pattern as the right-hand operand of `&`:
> Pattern & PatternOperandOrGuard
> As a result, if we curry and squint, we can see that “& Guardpattern” is a map from patterns to patterns. We can also see that “& Pattern” is a map from patterns to patterns; and finally we can appreciate two other points: (1) GuardPattern need not ever actually be regarded as a patterns, and (2) we have overloaded `&` to mean two rather different things.
Yes. There are parsing problems if we try to take such an overload
seriously. I think we need two kinds of “&”, one which says
“stay in pattern land” and one which says “here’s an expression”.
Another way to factor this would be an explicit marker (such
as “true” was proposed to be) which says, “here’s an expression,
just this one place”.
P & Q
P with G
P && G
P & __Expr G
> While it is possible to express the fact that a guard construct is a map from patterns to patterns by insisting that a guard is itself a pattern and then using the pattern conjunction operator, this is not the only way to express or model that fact.
> Now, the quoted BNF has reached its current structure because, as the JEP carefully explains,
> The grammar has been carefully designed to exclude a guard pattern as a valid
> top-level pattern. There is little point in writing pattern matching code such as
> o instanceof true(s.length != 0). Guard patterns are intended to be refine
> the meaning of other patterns. The grammar reflects this intuition.
> As a result, a guard necessarily appears to the right of a `&` and therefore necessarily to the right of a pattern. We should also inquire as to whether it is ever desirable in practice, within a chain of `&` (pattern conditional-and) operations for a pattern to appear to the right of a guard. If not, then `&` chains always have the simple form
> pattern & pattern & … & pattern & guard & guard & … & guard
> where the number of patterns must be positive but the number of guards may be zero. And if this is the case, it is not unreasonable to ask whether readability might not be better served by better marking that transition from patterns to guard in the chain, for example:
> pattern & pattern & … & pattern when guard & guard & … & guard
> And then we see that there really is no reason to try to overload `&` (however it is actually spelled) to mean both pattern conjunction and guard conjunction, because guard conjunction already exists in the form of the `&&` expression operator:
> pattern & pattern & … & pattern when guard && guard && … && guard
s/when/&&/ is my proposal; then &&guard is the visual cue,
and you can stack them. (If you want || or ?:, you will need
to use parens.)
> and therefore we can, after all, simplify this general form to the case of zero or one guards:
> pattern & pattern & … & pattern [when guard]
> Finally, given that (an earlier version of) the patterns design already encompasses forms that can bind the entire object as well as components (what is done in other languages with `as`, I have to ask: what are the envisioned practical applications of pattern conjunction other than as a cute way to include guards or a (more verbose) way to bind the entire value as well as components? Maybe as a way to fake intersection types?
> Now, all of this has no bearing on whether or not guards are required to be “top level only” in all cases; it argues only that guards need not appear within pattern-conjunction chains. But I believe it would be perfectly reasonable to write
> case Point(int x when x > 0, int y when y > x):
> Rémi has argued that this would be better written
> case Point(int x, int y) when x > 0 && y > x:
> but I would argue that this choice is, and should be, a matter of style, and when matching against a record with many fields it might be more readable to mention each field’s constraint next to its binding rather than to make the reader compare a list of bindings against a list of constraints.
I agree. Having a less restrictive language lets bad
coders code badly, but lets the rest of us code
> So what I would like to see is the convincing application example where you really do want to write
> pattern & guard & pattern
> because then everything I’ve written above falls to the ground.
Not completely: You can write one of:
pattern & (var p && guard(p)) & pattern
pattern & (var _ && guard) & pattern
> But if we cannot come up with such an example, then perhaps what I have written should be examined carefully as a serious design alternative.
I think you are on the right track.
BTW, we may have a need in two places for syntax which escapes
from pattern syntax. Patterns and expressions are distinct
sub-languages, and sometimes we want to nest one inside
the other. The “instanceof” operator escapes from expression
land to pattern land. The “&&” or “with” or “if” operator escapes
from pattern land to expression land, permanently at the right edge.
We will also need an escape which marks in-args to static
patterns, as “withMapping(? inarg, var outarg)”.
More information about the amber-spec-experts