[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