Finalizing in JDK 16 - Pattern matching for instanceof

Guy Steele guy.steele at oracle.com
Wed Aug 26 18:37:42 UTC 2020



> On Aug 26, 2020, at 11:00 AM, Brian Goetz <brian.goetz at oracle.com> wrote:
> 
> I have been thinking about this and I have two refinements I would like to suggest for Pattern Matching in instanceof.  Both have come out of the further work on the next phases of pattern matching.  
> 
> 
> 1.  Instanceof expressions must express conditionality.  
> 
> One of the uncomfortable collisions between the independently-derived pattern semantics and instanceof semantics is the treatment of total patterns.  Instanceof always says "no" on null, but the sensible thing on total patterns is that _strongly total patterns_ match null.  This yields a collision between 
> 
>     x instanceof Object
> and
>     x instanceof Object o
> 
> This is not necessarily a problem for the specification, in that instanceof is free to say "when x is null, we don't even test the pattern."  But it is not good for the users, in that these two things are subtly different.  
> 
> While I get why some people would like to bootstrap this into an argument why the pattern semantics are wrong, the key observation here is: _both of these questions are stupid_.  So I think there's an obvious way to fix this so that there is no problem here: instanceof must ask a question.  So the second form would be illegal, with a compiler error saying "pattern always matches the target."  
> 
> Proposed: An `instanceof` expression must be able to evaluate to both true and false, otherwise it is invalid.  This rules out strongly total patterns on the RHS.  If you have a strongly total pattern, use pattern assignment instead.  

Makes sense to me, but one question: would this restriction "must be able to evaluate to both true and false” be applied to _every_ `instanceof` expression, or only those that have a pattern to right of the `instanceof` keyword?  I ask because if it is applied to _every_ `instanceof` expression, this would represent an incompatible change to the behavior of `x instanceof Object`, among others.  Is it indeed the intent to make an incompatible change to the language?

> 2.  Mutability of binding variables.  
> 
> We did it again; we gave in to our desire to try to "fix mistakes of the past", with the obvious results.  This time, we did it by making binding variables implicitly final.  
> 
> This is the same mistake we make over and over again with both nullity and finality; when a new context comes up, we try to exclude the "mistakes" (nullability and mutability) from those contexts.  
> 
> We've seen plenty of examples recently with nullity.  Here's a historical example with finality.  When we did Lambda, some clever fellow said "we could make the lambda parameters implicitly final."  And there was a round of "ooh, that would be nice", because it fed our desire to fix mistakes of the past.  But we quickly realized it would be a new mistake, because it would be an impediment to refactoring between lambdas and inner classes, and undermined the mental model of "a lambda is just an anonymous method."  
> 
> Further, the asymmetry has a user-model cost.  And what would be the benefit?  Well, it would make us feel better, but ultimately, would not have a significant impact on accidental-mutation errors because the context was so limited (and most lambdas are small anyway.)  In the end, it would have been a huge mistake.  
> 
> 
> I now think that we have done the same with binding variables.  Here are two motivating examples:
> 
> (a) Pattern assignment.  For (weakly) total pattern P, you will be able to say
> 
>     P = e
> 
> Note that `int x` and `var x` are both valid patterns and local variable declarations; it would be good if pattern assignment were a strict generalization of local variable declaration.  The sole asymmetry is that for pattern assignment, the variable is final.  Ooops.  
> 
> (b) Reconstruction.  We have analogized that a `with` expression:
> 
>     x with { B }
> 
> is like the block expression:
> 
>     { X(VARS) = x; B /* mutates vars */; yield new X(VARS) }
> 
> except that mutating the variables would not be allowed.  
> 
> From a specification perspective, there is nontrivial spec complexity to keep pattern variables and locals separately, but some of their difference is gratuitous (mutability.)  If we reduce the gratuitious differences, we can likely bring them closer together, which will reduce friction and technical debt in the future.  
> 
> 
> Like with lambda parameters, I am now thinking that we gave in to the base desire to fix a past mistake, but in a way that doesn't really make the language better or safer, just more complicated.  Let's back this one out before it really bites us.

I agree with this analysis.  It does suggest that we should consider whether to extend the syntax of a type pattern from `T x` to `[final] T x`, and the syntax of a deconstruction pattern from `D(Q) [v]` to `[final] D(Q) [v]` (in the latter case, `final` may be present only if `v` is also present), so that the user can choose to mark a pattern binding variable as `final`.  (This is something that could be added right away or later.)

So one could choose to write such things as:

	x instanceof final String s

	Box(final Frog f) = …;

	final Box(final Frog f) b = …;

	Box(Bag(final Frog f)) = …;

	Box(final Bag(var x) theBag) = …;


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20200826/91f56661/attachment-0001.htm>


More information about the amber-spec-experts mailing list