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

forax at univ-mlv.fr forax at univ-mlv.fr
Tue Mar 9 18:02:38 UTC 2021

----- Mail original -----
> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "Gavin Bierman" <gavin.bierman at oracle.com>, "amber-spec-experts" <amber-spec-experts at openjdk.java.net>
> Envoyé: Jeudi 4 Mars 2021 17:28:05
> Objet: Re: [External] : Re: Two new draft pattern matching JEPs

>> I want to separate the discussions about & between patterns and true()/false()
>> aka mix pattern and expressions,
>> because the later is a call for trouble for me, the former is just a question
>> about adding a new pattern or not.
>> I see true() and false() has an heresy because conceptually a bunch of patterns
>> is a kind of declarative API while expressions are not.
> If it helps, let me trace the story of where the true/false pattern idea
> came from.  We have had "method patterns" (need a better name) in the
> model forever; we have anticipated that some method patterns (e.g., map
> get, regex match) will distinguish between input and output parameters.
> And we have imagined AND and OR combinators for a long time as well.
> One day, it occurred to me that, with the ultimate pattern model we are
> envisioning, we don't need a linguistic notion of guards at all!  I
> could write an ordinary pattern:
>     static pattern guard(boolean b) {
>         if (!b) __FAIL;
>     }
> and voila, we can express guards as:
>     case P & guard(e):
> This was a powerful realization, because it meant that the notion of
> "guard" was "not a primitive"; it was something we could compose from
> existing envisioned tools.  Except, we don't have method patterns yet
> (and are not ready to do them), so ... why not treat the existing
> reserved identifier `true` as if it were declared like `guard` above?
> (What's weird about this pattern is that it ignores its target. That's
> "clever", and clever can be confusing.)

This open a lot of questions in my mind.
As you said there is no target, but also it seems that the argument of the boolean parameter can also access to the previous bindings, something which is far from obvious to me. Until now the bindings were only available after ':' or '->' for a case, i.e after the pattern part, not in between patterns. That seems a huge leap to me.

You said in an earlier email that apart the target, the parameters of a pattern should be a kind of constant, you mention something along the side of effectively final,
here you go full steam in the opposite direction saying a pattern can take as parameter an expression created from the bindings available at that point.

If we go in that direction, i'm not sure to understand what is a pattern anymore, what is the difference between a pattern and a method in that case, why we should we have pattern methods in a class because it seems that a there are taking expression as parameter as the classical methods.

In that case, perhaps every "pattern" should take an expression, and the expression on which the switch is done should have a default binding like 'it' so we can write something like
  switch(o) {
    case (var a, var b) = Foo.destruct(it) -> 
or using something like the walrus operator
  switch(o) {
    case Foo.destruct(it) := var a, var b -> 
if we want to keep the bindings on the right side, after the pattern itself.

So we have a consistent view, a pattern can takes bindings, 'it' is the default binding, there is no need to a pattern method because this is just syntactic sugar on top of a classical method call.

> I don't say this to justify the syntax; I get that pressing `true` into
> service may feel a bit of a stretch.  My point here is that the
> true/false patterns proposed in the JEP are *not special*; the are
> something we could declare eventually with the model that we expect to
> get to.  (They're like record patterns, in a sense; eventually all
> classes will be able to declare deconstruction patterns, but records are
> special in that they get one even without declaring it.)  So if you find
> them heretical, then you should also have a problem with expressing
> map-get or regex match as a pattern, no? And if so, let's put the
> complaint where it belongs; the use of expressions in true/false is just
> a symptom of your distress, not the cause.  Let's talk about the cause.
>> Allowing expression right inside a pattern means that you can witness the
>> evaluation order of patterns, something we don't want.
> Note that this is unavoidable when we get to declared patterns; if there
> are side effects, you can witness their order and arity.  But, this is
> also not your real point.  So, let's get to the real point, so we can
> discuss that, because whether we leak an unspecified order of evaluation
> through side-effects is mostly a distraction.
>> There is a misunderstanding here, i'm referring to the fact that
>>    case Point(1, 1):
>> is actually rejected because it's too much like new Point(1,1) but at the same
>> time, you want to allow expressions in the middle of patterns.
> Actually, the opposite!
> A lot of people (initially, me included) would like to just interpret a
> boolean expression as a pattern:
>     case Foo(var x, var y) && x > y: ...
> I found that goal compelling, but as patterns get more complicated, this
> gets unreadable.  A main benefit of the true() patterns (or, the
> explicit guard() pattern declared above) is that it "quarantines" the
> expression in an expression wrapper; there's a clear boundary between
> "pattern world" and "expression world".
> In any case, you are distorting the claim of "no expressions in the
> middle of patterns."  We have always envisioned specific contexts where
> expressions can be dropped into patterns (map get, regex), but we have
> worked to ensure there is a *clear syntactic division*, so that it is
> clear from the syntactic structure whether something is a pattern or an
> expression.

see above, being able to access to the bindings as argument of a pattern is something new to me and kind a in between two states, it's not fully a pattern where the matching and the binding are not related (apart the target) and it's not fully an expression too.

>> For (a), yes it's switch specific and it's great because we don't need it for
>> instanceof, you can already use && inside the if of an instanceof and you don't
>> need it when declaring local variables because the pattern has to be total. So
>> being specific to switch is not really an issue.
> Only true if `instanceof` and `switch` are the only contexts where you
> can imagine patterns. What about, say, `catch`?  If you nail a bag on
> the side of switch, you will have to nail the same bag on the side of
> catch.  Which might be acceptable, and the same bag might fit, but
> that's yet more ad-hoc language surface.
>> For (b), you can always shift all the contents of true() and false() to the
>> right into a traditional guard, so we don't need true() and false()
> In theory true, but only in a world where pattern evaluation is
> cost-free, exception-free, and side-effect free.  (That's why && is
> short circuiting -- because these things are not always true.)
> I'm not deaf to the argument that "nailing a bag on the side will be
> easier for developers to accept", but please, let's stop pretending it's
> not a bag.  If you want to advocate for "bag", then please, make that
> case directly!

It's advocating to have three separates zones that all have a well defined semantics,
the first part (the pattern part) is a pattern wich can have sub-pattern a provides bindings,
the second optional part (the guard part) is an expression that can access to the binding to refine the pattern,
the last part is either en expression of a block of code as in a classical switch.

And yes, you are losing the short-circuit ability, because the guard expression is pushed on the right side,
but you keep a clean separation of the different zones.


More information about the amber-spec-experts mailing list