> On Aug 26, 2020, at 11:00 AM, Brian Goetz <[email protected]> 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) = …;


Reply via email to