Finalizing in JDK 16 - Pattern matching for instanceof
Remi Forax
forax at univ-mlv.fr
Sun Aug 30 10:09:26 UTC 2020
> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "Gavin Bierman" <gavin.bierman at oracle.com>, "amber-spec-experts"
> <amber-spec-experts at openjdk.java.net>
> Envoyé: Mercredi 26 Août 2020 17:00:47
> Objet: Re: Finalizing in JDK 16 - Pattern matching for instanceof
> 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.
I agree,
> 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,
but in that case, does it also mean that "final" as modifier should be allowed ?
if (foo instanceof final Bar b) { ... }
Rémi
> On 7/27/2020 6:53 AM, 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.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20200830/c5f62ca8/attachment-0001.htm>
More information about the amber-spec-experts
mailing list