[External] : Re: Fwd: Two new draft pattern matching JEPs

Brian Goetz brian.goetz at oracle.com
Thu Mar 4 21:09:45 UTC 2021


Thanks Alan, this kind of confirms what I was thinking.

I agree that while it might seem "silly" that we react to `true(x)` as 
being contrary to our expectations of truth, it is real, and after 
asking a few other sensible people who can sit on their 
bikeshed-impulses, seems fairly common.  So there's something to that.

Agree also that `false` adds relatively little, and may even subtract if 
people mix them.  So its there only "for consistency", and FC-arguments 
should always be suspect.

I'll think more about your wish again for "sub patterns"; I do expect it 
will be common to have things like:

    case P & g1:
    case P & g2:

and avoiding repeating P in all its glory seems a worthy goal. 
Eliminating the repetition is likely to be more readable and less 
error-prone, and, as a bonus, probably reduces the need for the compiler 
to try and be clever and transform the above into a form that avoids 
repeating P, because the user will already have said what they mean.  (I 
assume that your | trick works at an arbitrary nesting depth, which 
probably works better with a sig-whitespace language like Haskell.)


On the true-vs-grobble front, Victor's note pushed me to think some more 
about what a unification would look like, and it is not as pretty as 
first thought.  In the general case, a pattern has two argument lists: a 
set of input expressions, and a set of output bindings.  For most 
patterns, the former will be empty, and so we want to be able to elide 
it.  From a parsing perspective, what goes in the first list is 
Expression, but what goes in the second list is Pattern.  If we stick to 
the strawman:

     case Foo(inputArgs)(bindings):

we can easily define this so the first list is optional when empty.  But 
for grobble, it is the _second_ list that is empty, and we can't define 
a parser rule that allows either to be elided, unless the productions 
for expression and pattern are completely disjoint (which they are not 
yet.)  Of course, we could pick other syntaxes (#include 
"bikeshed-deterrent.h"), such as `Foo[inArgs](outBindings)`, which 
avoids this problem, but then a guard would look like `grobble[expr]`, 
which looks more like an array dereference than a guard.

All this is to say that, it may not be an easy lift to ultimately unify 
`grobble` as an ordinary declared pattern, but that was probably only a 
nice-to-have.  But what I'm taking from your comment (and others) is 
that some sort of non-boolean `grobble` would be OK as a special form, 
and probably less doubletake-inducing than true/false.

(BTW, I suspect the most common guard will be `true(x != null)`, for 
which having a special form `non-null(x)` might be nicer.)



On 3/4/2021 3:40 PM, Alan Malloy wrote:
> I have certainly experienced a double take when seeing true(expr). I 
> can step back and recognize that it's just some arbitrary syntactical 
> choice, and true(x) is evocative of the question "is x true?", but at 
> first glance it is not appealing. Repurposing a language keyword in 
> this way is surprising; it seems silly, but I think part of it is 
> like, "my IDE has always used a different font for true than for 
> method calls, and wouldn't it look weird to have something with that 
> font have parentheses after it?". Obviously IDEs will adapt, and to 
> complain about anticipated IDE font rendering would be even worse than 
> Brian's usual bugbear about premature syntax discussions. I'm just 
> getting at that true currently occupies a very specific place in my 
> mental headspace, and I'm not sure that I'm happy to stretch it if all 
> we get is the evocative "is x true?" reading.
>
> I also don't really care for the natural symmetry with false. I'd 
> rather have one way to express "protect this pattern-match clause with 
> a guard" than two - I never was very excited by perl's `unless` 
> keyword, for when you want to write an `if` but backward. If we had 
> true(expr), we'd surely be asked to add false(expr), which sounds like 
> an obvious feature but I don't imagine would lead to more readable 
> code very often.
>
> grobble seems fine. When discussion of switch patterns started, I was 
> one of the people clamoring (quietly) for guards, and I imagined 
> either a single operator or a single keyword separating the (entire) 
> pattern from its guard expression (singular). But the idea of seeing 
> guards as just a special kind of pattern, and for patterns to be 
> composable with each other as a more general kind of guard, appeals to 
> me. Being able to match two patterns against the same object 
> (and-patterns) is actually a feature I thought Haskell and Scala 
> already had, via their as-pattern - but it turns out you can only 
> actually name a variable, not an entire pattern, in their as-pattern 
> slot. In Clojure, at least, generally anywhere a variable name could 
> be bound, you can bind a destructuring form instead - and this 
> includes the :as slot. If we're going to have and-patterns (which, 
> again, I think are nice), it seems quite neat to have guards be just a 
> special case of that, and all it "costs" is that instead of a single 
> keyword or operator separating guards we have an operator and then a 
> keyword (later revealed to be an ordinary library pattern) separating 
> the pattern from its guard.
>
> The one objection I still have to grobble is one I've raised before: 
> it makes it hard to imagine ever representing disjunctions as guards. 
> I always bring up stuff like
>
> switch (people) {
>   case Pair(Person(String name1, int age1), Person(String name2, int 
> age2))
>     | age1 > age2 -> name1 + " is older"
>     | age2 > age1 -> name2 + " is older"
>     | otherwise -> format("%s and %s are the same age", name1, name2)
> }
>
> Three cases, but you don't have to repeat the entire pattern three 
> times, just guard the parts you care about. This makes it useful for 
> guards to not just be degenerate patterns, but to be their own 
> separate thing that can refine a pattern. With grobble and 
> and-patterns I don't see a nice way of spelling this very useful 
> feature. People often answer: "Just match the Pair once, extracting 
> its variables, and write an if/else chain under it", but that doesn't 
> combine very well with fall-through: if it's possible for all your 
> guard clauses to be false you'd like to fall through to the next 
> pattern (maybe when someone's name is "Brian" I want to fall through 
> to a pattern handling Collection instead of Pair).
>
> On Thu, Mar 4, 2021 at 10:19 AM Brian Goetz <brian.goetz at oracle.com 
> <mailto:brian.goetz at oracle.com>> wrote:
>
>     Lots of people (Remi here, VictorN on a-comments, StephenC on
>     a-dev) are complaining about true/false patterns, but my take is
>     that we've not really gotten to the real objections; instead, I'm
>     seeing mostly post-hoc rationalizations that try and capture the
>     objections (understandable), but I don't think we've really hit
>     the mark yet.  As I've said, I believe there might be something
>     "there" there, but the arguments made so far have not yet captured
>     it, so I don't really know how to proceed. But, clearly this has
>     pushed people's buttons, so let's try to drill in.
>
>
>     One possible objection is indirectness / discoverability; that to
>     refine a pattern, you have to find some other pattern that
>     captures the refinement, and combine them with an operator that
>     isn't used for anything else yet.  This contributes to making it
>     feel like an "incantation".
>
>     Another possible object is more superficial, but may be no less
>     real.  I had a private exchange with AndreyN, which revealed two
>     potentially useful bits of information:
>
>      - It's possible that the bad reaction to true(e) is that, because
>     we're so wired to seeing `true` as a constant, the idea of seeing
>     it as a method/pattern name is foreign;
>
>      - Because true is a reserved identifier, the possibility to later
>     pull back the curtain and say "look, true(e) is not magic, it's
>     just a statically imported declared pattern!" is limited.  So by
>     picking true/false now, we miss an opportunity to unify later.
>
>     So, here's a thought experiment, not so much as a concrete
>     proposal, but as a "how does this change how we think about it"
>     query; imagine we picked another identifier, with the plan of
>     ultimately exposing it to be just an ordinary method pattern
>     later. I'll use the obviously stupid "grobble" in this example, to
>     avoid inclinations to paint the shed.  So you'd write:
>
>         case Foo(var x, var y) & grobble(x > y): ...
>
>     and in Java 17, "grobble" would be specified to be an ad-hoc guard
>     pattern, but in Java N > 17, we would be able to pull back the
>     curtain and say "behold, the long-hidden declaration of grobble,
>     which we've conveniently static-imported for you":
>
>         static pattern(void) grobble(boolean expr) {
>             if (!expr)
>                 __FAIL;
>         }
>
>     This would allow us to borrow from the future, while allowing the
>     temporary hack to be turned into something legitimate later.
>
>     So, control question: if we had said "grobble" instead of "true",
>     does that change the perception of how ugly, foreign, or roundabout
>
>         case Foo(var x, var y) & grobble(x > y): ...
>
>     is?
>
>     Direct answers only, initially.
>
>     On 3/4/2021 12:05 PM, Brian Goetz wrote:
>>     Received on the -comments list.
>>
>>     Analysis from the legislative analyst:
>>
>>     This comment amounts to "Well, if you could eventually write the
>>     true/false patterns as declared patterns which ignore their
>>     target, then just do declared patterns now, and just make them
>>     declared patterns." (Which is exactly what kicked off this
>>     direction -- that guards could be expressed as declared patterns
>>     which ignore their target.)
>>
>>     When lumping the features together for a delivery, there's a
>>     balance to be struck, of delivering incremental value vs
>>     delivering the entire story at once.  The JEPs proposed at this
>>     point are pretty close to being a useful increment of value
>>     without overly constraining the remainder of the story, but
>>     guards are an area where it is tempting to "borrow from the
>>     future." Of course if we could do everything at once, we wouldn't
>>     be worrying about balancing the short term with the long. But,
>>     delaying further pattern matching progress until we have a full
>>     story for declared patterns seemed a bit extreme.
>>
>>     So it's not that we missed that route -- indeed, that's the route
>>     that got us to the current position -- it's just that route was
>>     rejected as "would delay delivering real value now."
>>
>>
>>     -------- Forwarded Message --------
>>     Subject: 	Re: Two new draft pattern matching JEPs
>>     Date: 	Thu, 4 Mar 2021 17:34:15 +0100
>>     From: 	Victor Nazarov <asviraspossible at gmail.com>
>>     <mailto:asviraspossible at gmail.com>
>>     To: 	amber-spec-comments at openjdk.java.net
>>     <mailto:amber-spec-comments at openjdk.java.net>
>>
>>
>>
>>     Hello Java experts,
>>
>>     I've been following the discussion about new JEPs for pattern
>>     matching and
>>     I've observed a controversy considering the introduction of
>>     Pattern guards.
>>
>>     It seems that what Brian Goetz stated as a problem is:
>>
>>>       * either we
>>>         don't provide a way to write guarded patterns now (which is not a
>>>         problem for instanceof, but is for switch), or
>>>     * we nail some bit of terrible syntax onto the switch that we're stuck
>>     with.
>>
>>     But from my understanding this misses another route:
>>
>>>     We've already discussed how some patterns (e.g., regex) will take input
>>>     arguments, which are expressions.  We haven't quite nailed down our
>>>     syntactic conventions for separating input expressions from output
>>>     bindings, but the notion of a pattern that accepts expressions as input
>>>     is most decidedly not outside the model.
>>
>>     When patterns with arguments become available users are able to
>>     write code
>>     like the following (with imaginary syntax).
>>
>>     ````
>>     String s = "aabb";
>>     String result = switch (s) {
>>     case String.["(a+)(b+)"]matches(var as, var bs) -> bs + as;
>>     default -> "no match";
>>     }
>>     ````
>>
>>     Having this ability nothing prevents users to define a `guard`
>>     pattern in
>>     their library and to use it like:
>>
>>     ````
>>     case Rectangle(Point x, Point y) & Utils.[x > 0 && y > 0]guard()
>>     ````
>>
>>     For me it seems a good solution to introduce a more general mechanism
>>     (patterns with input arguments) and use it to define a library
>>     `guard`
>>     pattern then to nail some additional syntax (true/false overload).
>>
>>     So returning to the original problem then I think a possible
>>     solution is to
>>     introduce some special `guard` library pattern right away.
>>
>>     Cons:
>>     * Need to decide on special syntax for input arguments right away
>>     * Hard to specify that custom patterns with input arguments are
>>     not yet
>>     available and only special library `guard` patterns can use this
>>     feature.
>>
>>     Pros:
>>     * Less special syntax in the language, because input arguments
>>     are going
>>     to be introduced anyway
>>     * It is probably easier to explain to users the usefulness of `&`
>>     because
>>     that way users can already see that not only destructuring
>>     pattern are
>>     going to be available, but more generic and complex patterns with
>>     input
>>     arguments are going to be available.
>>
>>     --
>>     Victor Nazarov
>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20210304/0eba9ff6/attachment-0001.htm>


More information about the amber-spec-experts mailing list