[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