This feels like we landed in a good place.  It preserves the underlying goal of the original approach -- that we can compose patterns with patterns, and patterns with boolean expressions, and doesn't require nailing bags onto switch (yay).  The main difference is that it creates a distinguished "guarded pattern" operator rather than asking users to compose guarded patterns from an expression-to-pattern operator and pattern-AND.

The main objections to the `P & true(e)` approach were the aesthetic reaction to this use of `true` (which was one of those first-five-minutes reactions, and people might well have gotten over it in the next five minutes), and the more serious discoverability problem of having to compose two (currently unfamiliar) features to get guarded patterns.  The current proposal seems a lower energy state, while deriving from the same basic principles.

FTR, the key observation that broke the jam was the observation that, if we treat & and && as:

    (&) :: Pattern -> Pattern -> Pattern
    (&&) :: Pattern -> Expression -> Pattern

we can, with parentheses, achieve arbitrary orderings of patterns and guard expressions, and are not forced to push all guards to the end (which was where we got stuck the last time we looked at &&.)

On 3/10/2021 9:47 AM, Gavin Bierman wrote:
Okay, so it seems like our initial stab at guards and a pattern conjunction
operator needs some finessing.

Here's another take, inspired by Guy's emails.

Step 1. Let's use `p && e` as the way to guard a pattern p with a boolean
expression e.

Step 2. [Now or later] Let's use `&` (and `|`) as the conjunction and
disjunction operators on patterns.

There are a couple of immediate parsing puzzlers:

* e instanceof String s && s.length() > 2

This parses as `(e instanceof String s) && s.length() > 2` today. We need to be
careful that our grammar continues to make this the case (see below). We will
also introduce a parenthesized pattern, which you can use if you want the
dangling `&& s.length() > 2` to parse as a guard for the `String s` type
pattern. (It is extremely fortuitous that the functional behavior of both
expressions is the same, but either way I think this is a simple rule.)

* case p && b -> c -> b

Now we have some ambiguity from the `->` in a lambda expression and in a switch
rule. Fortunately, again I think we can just lean into the grammar to get what
we want. At the moment, the grammar for expressions is:

     Expression:
         LambdaExpression
         AssignmentExpression

As a lambda expression can never be a boolean expression it can never
meaningfully serve as a guard for a pattern. Great!

So, I'd like to suggest this grammar for patterns (including pattern conjunction
and pattern disjunction operators for completeness but we can drop them from the
first release):

Pattern:
: ConditionalOrPattern
: ConditionalOrPattern `&&` Guard

ConditionalOrPattern:
: ConditionalAndPattern
: ConditionalOrPattern `|` ConditionalAndPattern

ConditionalAndPattern:
: PrimaryPattern
: ConditionalAndPattern `&` PrimaryPattern

PrimaryPattern:
: TypePattern
: RecordPattern
: ArrayPattern
: `(` Pattern `)`

Guard:
: AssignmentExpression

Along with the following change to the grammar for instanceof:

InstanceofExpression:
: RelationalExpression `instanceof` ReferenceType
: RelationalExpression `instanceof` PrimaryPattern      <-- Note!

Some consequences:

p1 & p2 & p3 && g1 && g2 parses as ((p1 & p2) & p3) && (g1 && g2), yay!

p1 && g1 & p2 && g2 needs to be bracketed as (p1 && g1) & (p2 && g2) to parse
properly. But that's okay, as I think the second is much clearer.

Let me know what you think.

Gavin

Reply via email to