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. More below: On Mar 5, 2021, at 2:12 PM, Guy Steele <guy.ste...@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.go...@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: > PatternOperand > Pattern & PatternOperandOrGuard > PatternOperandOrGuard: > PatternOperand > GuardPattern > > 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 more clearly. > … > 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)”. — John