Flow scoping
Brian Goetz
brian.goetz at oracle.com
Tue Dec 11 20:08:38 UTC 2018
(Note to observers: we did an internal survey so I could gauge the
comfort level of the EG with the flow scoping rules for pattern binding
variables. We'll do a public survey at some point in the future.)
Thanks for taking the time to take the quiz. The results were pretty
good. For the early examples, which were mostly about if-then and
short-circuiting booleans, people got the "right" answer (the answer the
current rules, at
http://cr.openjdk.java.net/~briangoetz/amber/pattern-semantics.html,
gives):
if (x instanceof String v) {
// x in scope here
}
else {
// x not in scope here
}
Note that we say "not in scope", not just "not DA". There are a number
of reasons for choosing this seemingly more complicated approach.
(These have been discussed here before; I'm mostly summarizing.) One
reason is the scoping for traditional "switch" gets in the way:
switch (x) {
case String v: ...; break;
case Integer v: ...; break;
case Long v: ...; break;
}
Since the body of a switch is one big scope, when we get to the second
case, `v` would already be in scope, and have type `String`. Similarly,
in an if-else block that represents the same computation:
if (x instanceof String v) { ... }
else if (x instanceof Integer v) { ... }
else if (x instanceof Long v) { ... }
(Note too, that it is an important design constraint that the rules
allow the above two to be freely refactored into each other.)
The flow-scoping -- where scoping of binding variables is _defined by_
the DA rules, means that where a binding variable is not sensible to
use, it is actually _not in scope_, making it free for reuse.
At first, this makes constructs like:
if (!(x instanceof String v) || v.length() == 0) {
// v not in scope
}
else {
// v in scope here
}
kind of scary; the "shape" of v's scope is not contiguous. But, I think
its a product of a series of forced moves. We want users to be able to
freely refactor `if (x) s; else t` into
`if (!x) t; else s`; if the language didn't allow that refactoring, that
would be seriously smelly.
The same is true with conditional expressions: `x ? e : f` should be
equivalent to `!x ? f : e`.
(Some of you are surely saying "but can't we just find an approximate
enclosing rectangular scope, and use that?" We tried, pretty hard. Not
only did it give us problems with the switch/if-else chain scoping, but
it meant that variables could be in scope _before_ their declaration --
which is also pretty weird.)
(Some of you are probably saying "can't we fix the switch/chain scoping
by relaxing the shadowing rules, perhaps to allow shadowing of a DU
variable? This too, has its own complexities.)
At least two of you spotted the trick question, which was #12:
if (x instanceof Comparable v && x instanceof Serializable v) { ... }
Flow scoping has two touch points with DA/DU, and I left out the second
from the instructions. The first is that a binding variable is in scope
iff it is DA at that point; the second is that a binding declaration is
illegal if the variable is not DU at that point. So the above is
illegal because we don't know `v` is DU in the second binding.
Before we get to the harder ones, can people share what made them
uncomfortable, for the ones where you marked "not comfortable with the
answer"?
More information about the amber-spec-observers
mailing list