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 <[email protected]
<mailto:[email protected]>> 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 <[email protected]>
<mailto:[email protected]>
To: [email protected]
<mailto:[email protected]>
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