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-experts mailing list