> 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) = …;