[patterns] reconsidering how the created variable name binds

Brian Goetz brian.goetz at oracle.com
Sun Dec 29 19:54:32 UTC 2019


(late to the party, John said most of these things already)

> An alternative proposal for how the variable declarations inherent in a
> pattern matching 'instanceof'/switch on expression's type bind:

In general, before we get into "proposals", it is generally good to back 
up and talk about what _problem_ we are trying to solve.  First we need 
to agree its a problem, then we need to agree it needs to be solved, 
then we need to game out possible solutions ... there's a lot of steps 
between here and there.

> The local variable created by such a construct (so, the 's' in: 'x
> instanceof String s') exists only within the lexical context within which
> it is created _AND_ only if it is definitely assigned.
>
>  From what I can tell by reading amber-spec-experts and amber-dev, right now
> the 2 considered alternatives are those two aspects by themselves; not
> combined.

The intention is that a binding variable is in scope when it _would be_ 
definitely assigned.  The similarity between the rules for DA and 
pattern scoping are not accidental.  You might think "why not just 
appeal to DA, then?".  But unfortunately we would have a circularity; 
you cannot compute DA until you know what is in scope, so you don't want 
the definition of scope to appeal to DA, lest you vanish in a poof of 
self-reference.

The notion of "within the lexical context in which it is created" is 
also problematic, as this is _too broad_ a scope; in the following, you 
wouldn't want `x` to be in scope at `A`:

     if (A && foo instanceof String x) { ... }

but it would be under the obvious interpretation of "within the lexical 
context of ...".   But again, let's start with what problem you're 
trying to solve.
> Here is the clear downside to the [B]-only option (the current draft spec),
> courtesy of Tagir Valeev (
> https://twitter.com/tagir_valeev/status/1210431331332689920 ):
>
>      static /*?final?*/ boolean FLAG = true;
>      static String v = "field";
>      public void test() {
>          String obj = "Pattern match";
>          if (!(obj instanceof String v)) {
>              // This branch is never taken.
>              while (FLAG) ; // endless
>          }
>
>          System.out.println(v);
>
> this will print 'field' if the FLAG variable isn't final, but it prints
> 'Pattern match' if it is. And that is correct according to the draft spec –
> that's what the [B] choice means. I take it we can all agree this is
> unfortunate.

OK, I'll agree on the unfortunate part.  But I'm not there on "so we 
have to change something" yet.  We should evaluate the design based on 
how small and obscure we can make the unfortunate parts (so far, we're 
doing great on the obscurity count!)

> The reason Gavin gave for not going with [A]-only is that it makes this
> code not compile:
>
>      if (x instanceof Point p) {
>      } else {
>          if (y instanceof Point p) {}
>      }
>
> Because the second instanceof is re-using the name of an existing and
> in-scope local variable declaration, and currently java doesn't let you
> shadow a local with a local, and Gavin feels (probably correctly) that
> allowing shadowing in such cases is itself confusing and a high-impact
> change. It really messes with switch too, forcing you to think up a
> different name for every case.

Yes, I am absolutely sure users will hate us if they have to make up a 
new binding name for every single arm of an if-else chain / pattern 
switch.  Not subjecting users to this was a core design requirement.

Not only will users hate it, but it will also be extremely common; I 
don't think its much of an exaggeration to say the case Gavin gave will 
inconvenience users in _every nearly single extended pattern switch 
chain_, whereas Tagir's case will come up about once in a billion 
times.  So we have to weight the thing that will annoy every user, ever 
time, much more strongly than the 
amazingly-clever-but-unlikely-to-happen-in-reality puzzler that Tagir 
gifted us with :)

> But, combine the two requirements and you suffer neither downside.
>
>
>   -- example 3 --
>
>      if (!(obj instanceof String newDecl)) {
>      }
>
> newDecl exists nowhere.

OK, here's where you collide with the current model.  In the current 
model, if the body of the `if` doesn't complete normally, `newDecl` is 
in scope after the `if`.  This is because we want to allow refactoring:

     if (e) {
         throw ...
     }
     else {
         S
     }

to

     if (e) {
         throw ...
     }
     S

This case was also very important to us; these two forms should be 
interchangeable.

> * Is it bending over backwards to prevent an 'academic' java puzzler?
Pretty sure this is a yes :)

All that said, your concern is also related to another common one, which 
is: is the complex flow-based scoping necessary, or couldn't we just 
hoist the bindings into some containing "rectangular" scope, and let DU 
do its job?

We spent a lot of time on this question.  It came down to the 
variable-reuse thing; the current scoping of switch (unlike in C#) 
doesn't allow us to restrict the scope of a variable to an "arm" of a 
case block -- because the notion of "case" block doesn't really exist in 
the language.  The body of a switch statement is just one big scope.




More information about the amber-dev mailing list