Finalizing in JDK 16 - Pattern matching for instanceof

Fred Toussi fredt at users.sourceforge.net
Mon Jul 27 12:32:27 UTC 2020


The proposed scoping rule, which allows what you call the "swiss cheese property" goes against existing Java rules, is a recipe for future bugs, and should not be allowed.

>     // field p is in scope
> 
>     if (e instanceof Point p) {
>         // p refers to the pattern variable
>     } else {
>         // p refers to the field
>     }
> 

Compare with the existing scoping rule for inner block variables and "for" variables, which does not allow a declared field to be re-declared in inner blocks.

        int val = 0;
        
        {
            int val = 1; // not allowed
        }
        
        for (int val = 0; val < 2; val++) { // not allowed
            
        }

Fred

On Mon, Jul 27, 2020, at 11:53, Gavin Bierman wrote:
> In JDK 16 we are planning to finalize two JEPs:
> 
>   - Pattern matching for `instanceof`
>   - Records
> 
> Whilst we don't have any major open issues for either of these features, I would
> like us to close them out. So I thought it would be useful to quickly summarize
> the features and the issues that have arisen over the preview periods so far. In
> this email I will discuss pattern matching; a following email will cover the
> Records feature.
> 
> Pattern matching
> ----------------
> 
> Adding conditional pattern matching to an expression form is the main technical
> novelty of our design of this feature. There are several advantages that come
> from this targeting of an expression form: First, we get to refactor a very
> common programming pattern:
> 
>     if (e instanceof T) {
>         T t = (T)e;         // grr...
>         ...
>     }
> 
> to
> 
>     if (e instanceof T t) {
>                             // let the pattern matching do the work!
>         ...
>     }
> 
> A second, less obvious advantage is that we can combine the pattern matching
> instanceof with other *expressions*. This enables us to compactly express things
> with expressions that are unnecessarily complicated using statements. For
> example, when implementing a class Point, we might write an equals method as
> follows:
> 
>     public boolean equals(Object o) {
>         if (!(o instanceof Point))
>             return false;
>         Point other = (Point) o;
>         return x == other.x 
>             && y == other.y;
>     }
> 
> Using pattern matching with instanceof instead, we can combine this into a
> single expression, eliminating the repetition and simplifying the control flow:
> 
>     public boolean equals(Object o) {
>         return (o instanceof Point other)
>             && x == other.x
>             && y == other.y;
>     }
> 
> The conditionality of pattern matching - if a value does not match a pattern,
> then the pattern variable is not bound - means that we have to consider
> carefully the scope of the pattern variable. We could do something simple and
> say that the scope of the pattern variable is the containing statement and all
> subsequent statements in the enclosing block. But this has unfortunate
> 'poisoning' consequences, e.g.
> 
>     if (a instanceof Point p) {
>         ...
>     } 
>     if (b instanceof Point p) {         // ERROR - p is in scope
>         ...
>     }
> 
> In other words in the second statement the pattern variable is in a poisoned
> state - it is in scope, but it should not be accessible as it may not be
> instantiated with a value. Moreover, as it is in scope, we can't declare it
> again. This means that a pattern variable is 'poisoned' after it is declared, so
> the pattern-loving programmer will have to think of lots of distinct names for
> their pattern variables. 
> 
> We have chosen another way: Java already uses flow analysis - both in checking
> the access of local variables and blank final fields, and detecting unreachable
> statements. We lean on this concept to introduce the new notion of flow scoping.
> A pattern variable is only in scope where the compiler can deduce that the
> pattern has matched and the variable will be bound. This analysis is flow
> sensitive and works in a similar way to the existing analyses. Returning to our
> example:
> 
>     if (a instanceof Point p) {
>         // p is in scope   
>         ... 
>     } 
>     // p not in scope here
>     if (b instanceof Point p) {     // Sure!
>             ...
>     }
> 
> The motto is "a pattern variable is in scope where it has definitely matched".
> This is intuitive, allows for the safe reuse of pattern variables, and Java
> developers are already used to flow sensitive analyses. 
> 
> As pattern variables are treated in all other respects like normal variables
> -- and this was an important design principle -- they can shadow fields.
> However, their flow scoping nature means that some care must be taken to
> determine whether a name refers to a pattern variable declaration shadowing a
> field declaration or a field declaration. 
> 
>     // field p is in scope
> 
>     if (e instanceof Point p) {
>         // p refers to the pattern variable
>     } else {
>         // p refers to the field
>     }
> 
> We call this unfortunate interaction of flow scoping and shadowing the "Swiss
> cheese property". To rule it out would require ad-hoc special cases or more
> features, and our sense is that will not be that common, so we have decided to
> keep the feature simple. We hope that IDEs will quickly come to help programmers
> who have difficulty with flow scoping and shadowing.


More information about the amber-spec-observers mailing list