From brian.goetz at oracle.com Mon Jul 20 16:59:18 2020 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 20 Jul 2020 12:59:18 -0400 Subject: Record copy()/with() In-Reply-To: References: <516632957.740296.1590233742384.JavaMail.zimbra@u-pem.fr> Message-ID: <050d03ed-fd0a-1a5c-55b2-c23e53fffd44@oracle.com> > Hello, Sergei! > > I see a number of problems with your proposed approach. > > 1. Naming. Java never specified any kind of derived identifiers using > prefixes/suffixes/etc. And, in my opinion, it's for good, because such > things make the spec dirty. Yes, this was one of the driving principles behind the record design.? While `withX()` is a reasonable library convention (though note that JavaBeans suggests `isX()` if x is boolean), it is a terrible convention for a language to force on its users.? The compromise we ended up at is that we can get away with `x()` as a name for a read accessor, because fields and methods are in different namespaces and you really can't argue that if `x` is a good field name, it's a bad accessor name -- but this is as far as we can go with "picking a convention." This is a digression from your point, but we can take this a little farther, if we want, by recognizing that a deconstruction pattern is really like a "multi-accessor", and we could, if we wanted, derive accessor methods `x()` and `y()` from a deconstruction pattern with bindings x and y.? There are a few challenges to clear out first, but it will be nice if we can say "you thought a record acquired accessors, but really what is happening is it acquires a canonical deconstruction pattern, and the accessors are derived from that." This is one possible component of the "push the record goodies down into all classes" program. But, the problem of "withers" is an important one, not only for records, but for inline classes and for ordinary immutable class. Telling people "just declare more accessors" would feel a step in the wrong direction.? Our C# friends, leaning on properties, are introducing a `with x { ... }` construct in C# 9, which is one way to get there, and which I'm investigating -- but again, there are some roadblocks to clear before we can get there.? Working on it. From brian.goetz at oracle.com Thu Jul 23 16:20:01 2020 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 23 Jul 2020 12:20:01 -0400 Subject: Next up for patterns: type patterns in switch In-Reply-To: References: Message-ID: <8a0d00c7-07a4-2d1e-8570-7e2cebd7f00e@oracle.com> In case I wasn't clear, this was a proposal to proceed on _right now_.? There's been no comments so far; should I take that as "perfect, ship it?" On 6/24/2020 10:44 AM, Brian Goetz wrote: > There are a lot of directions we could take next for pattern > matching.? The one that builds most on what we've already done, and > offers significant incremental expressiveness, is extending the type > patterns we already have to a new context: switch.? (There is still > plenty of work to do on deconstruction patterns, pattern assignment, > etc, but these require more design work.) > > Here's an overview of where I think we are here. > > [JEP 305][jep305] introduced the first phase of [pattern > matching][patternmatch] > into the Java language.? It was deliberately limited, focusing on only > one kind > of pattern (type test patterns) and one linguistic context (`instanceof`). > Having introduced the concept to Java developers, we can now extend > both the > kinds of patterns and the linguistic context where patterns are used. > > ## Patterns in switch > > The obvious next context in which to introduce pattern matching is > `switch`;? a > switch using patterns as `case` labels can replace `if .. else if` > chains with > a more direct way of expressing a multi-way conditional. > > Unfortunately, `switch` is one of the most complex, irregular > constructs we have > in Java, so we must teach it some new tricks while avoiding some > existing traps. > Such tricks and traps may include: > > **Typing.**? Currently, the operand of a `switch` may only be one of the > integral primitive types, the box type of an integral primitive, > `String`, or an > `enum` type.? (Further, if the `switch` operand is an `enum` type, the > `case` > labels must be _unqualified_ enum constant names.)? Clearly we can > relax this > restriction to allow other types, and constrain the case labels to only be > patterns that are applicable to that type, but it may leave a seam of > "legacy" > vs "pattern" switch, especially if we do not adopt bare constant > literals as > the denotation of constant patterns.? (We have confronted this issue > before with > expression switch, and concluded that it was better to rehabilitate > the `switch` > we have rather than create a new construct, and we will make the same > choice > here, but the cost of this is often a visible seam.) > > **Parsing.**? The grammar currently specifies that the operand of a > `case` label > is a `CaseConstant`, which casts a wide syntactic net, later narrowed with > post-checks after attribution.? This means that, since parsing is done > before we > know the type of the operand, we must be watchful for ambiguities between > patterns and expressions (and possibly refine the production for > `case` labels.) > > **Nullity.**? The `switch` construct is currently hostile to `null`, > but some > patterns do match `null`, and it may be desirable if nulls can be handled > within a suitably crafted `switch`. > > **Exhaustiveness.**? For switches over the permitted subtypes of > sealed types, > we will want to be able to do exhaustiveness analysis -- including for > nested > patterns (i.e., if `Shape`? is `Circle` or `Rect`, then `Box(Circle > c)` and > `Box(Rect r)` are exhaustive on `Box`.) > > **Fallthrough.**? Fallthrough is everyone's least favorite feature of > `switch`, > but it exists for a reason.? (The mistake was making fallthrough the > default > behavior, but that ship has sailed.)? In the absence of an OR pattern > combinator, one might find fallthrough in switch useful in conjunction > with > patterns: > > ``` > case Box(int x): > case Bag(int x): > ??? // use x > ``` > > However, it is likely that we will, at least initially, disallow > falling out > of, or into, a case label with binding variables. > > #### Translation > > Switches on primitives and their wrapper types are translated using the > `tableswitch` or `lookupswitch` bytecodes; switches on strings and > enums are > lowered in the compiler to switches involving hash codes (for strings) or > ordinals (for enums.) > > For switches on patterns, we would need a new strategy, one likely > built on > `invokedynamic`, where we lower the cases to a densely numbered `int` > switch, > and then invoke a classifier function with the operand which tells us > the first > case number it matches.? So a switch like: > > ``` > switch (o) { > ??? case P: A > ??? case Q: B > } > ``` > > is lowered to: > > ``` > int target = indy[BSM=PatternSwitch, args=[P,Q]](o) > switch (target) { > ??? case 0: A > ??? case 1: B > } > ``` > > A symbolic description of the patterns is provided as the bootstrap > argument > list, which builds a decision tree based on analysis of the patterns > and their > target types. > > #### Guards > > No matter how rich our patterns are, it is often the case that we will > want > to provide additional filtering on the results of a pattern: > > ``` > if (shape instanceof Cylinder c && c.color() == RED) { ... } > ``` > > Because we use `instanceof` as part of a boolean expression, it is easy to > narrow the results by conjoining additional checks with `&&`.? But in > a `case` > label, we do not necessarily have this opportunity.? Worse, the > semantics of > `switch` mean that once a `case` label is selected, there is no way to say > "oops, forget it, keep trying from the next label". > > It is common in languages with pattern matching to support some form > of "guard" > expression, which is a boolean expression that conditions whether the case > matches, such as: > > ``` > case Point(var x, var y) > ??? __where x == y: ... > ``` > > Bindings from the pattern would have to be available in guard expressions. > > Syntactic options (and hazards) for guards abound; users would > probably find it > natural to reuse `&&` to attach guards to patterns; C# has chosen > `when` for > introducing guards; we could use `case P if (e)`, etc. Whatever we do > here, > there is a readability risk,? as the more complex guards are, the > harder it is > to tell where the case label ends and the "body" begins.? (And worse > if we allow > switch expressions inside guards.) > > An alternate to guards is to allow an imperative `continue` statement in > `switch`, which would mean "keep trying to match from the next > label."? Given > the existing semantics of `continue`, this is a natural extension, but > since > `continue` does not currently have meaning for switch, some work would > have to > be done to disambiguate continue statements in switches enclosed in > loops.? The > imperative version is strictly more expressive than most reasonable > forms of the > declarative version, but users are likely to prefer the declarative > version. > > ## Nulls > > Almost no language design exercise is complete without some degree of > wrestling > with `null`.? As we define more complex patterns than simple type > patterns, and > extend constructs such as `switch` (which have existing opinions about > nullity) > to support patterns, we need to have a clear understanding of which > patterns > are nullable, and separate the nullity behaviors of patterns from the > nullity > behaviors of those constructs which use patterns. > > ## Nullity and patterns > > This topic has a number of easily-tangled concerns: > > ?- **Construct nullability.**? Constructs to which we want to add pattern > ?? awareness (`instanceof`, `switch`) already have their own opinion about > ?? nulls.? Currently, `instanceof` always says false when presented with a > ?? `null`, and `switch` always NPEs.? We may, or may not, wish to > refine these > ?? rules in some cases. > ?- **Pattern nullability.**? Some patterns clearly would never match > `null` > ?? (such as deconstruction patterns), whereas others (an "any" > pattern, and > ?? surely the `null` constant pattern) might make sense to match null. > ?- **Refactoring friendliness.**? There are a number of cases that we > would like > ?? to freely refactor back and forth, such as certain chains of `if > ... else if` > ?? with switches. > ?- **Nesting vs top-level.**? The "obvious" thing to do at the top > level of a > ?? construct is not always the "obvious" thing to do in a nested > construct. > ?- **Totality vs partiality.**? When a pattern is partial on the > operand type > ?? (e.g., `case String` when the operand of switch is `Object`), it is > almost > ?? never the case we want to match null (except in the case of the `null` > ?? constant pattern), whereas when a pattern is total on the operand > type (e.g., > ?? `case Object` in the same example), it is more justifiable to match > null. > ?- **Inference.**? It would be nice if a `var` pattern were simply > inference for > ?? a type pattern, rather than some possibly-non-denotable union. > > As a starting example, consider: > > ``` > record Box(Object o) { } > > Box box = ... > switch (box) { > ??? case Box(Chocolate c): > ??? case Box(Frog f): > ??? case Box(var o): > } > ``` > > It would be highly confusing and error-prone for either of the first two > patterns to match `Box(null)` -- given that `Chocolate` and `Frog` > have no type > relation, it should be perfectly safe to reorder the two. But, because > the last > pattern seems so obviously total on boxes, it is quite likely that > what the > author wants is to match all remaining boxes, including those that > contain null. > (Further, it would be terrible if there were _no_ way to say "Match > any `Box`, > even if it contains `null`.? (While one might initially think this > could be > repaired with OR patterns, imagine that `Box` had _n_ components -- > we'd need to > OR together _2^n_ patterns, with complex merging, to express all the > possible > combinations of nullity.)) > > Scala and C# took the approach of saying that "var" patterns are not > just type > inference, they are "any" patterns -- so `Box(Object o)` matches boxes > containing a non-null payload, where `Box(var o)` matches all boxes.? This > means, unfortunately, that `var` is not mere type inference -- which > complicates > the role of `var` in the language considerably.? Users should not have > to choose > between the semantics they want and being explicit about types; these > should be > orthogonal choices.? The above `switch` should be equivalent to: > > ``` > Box box = ... > switch (box) { > ??? case Box(Chocolate c): > ??? case Box(Frog f): > ??? case Box(Object o): > } > ``` > > and the choice to use `Object` or `var` should be solely one of > whether the > manifest types are deemed to improve or impair readability. > > #### Construct and pattern nullability > > Currently, `instanceof` always says `false` on `null`, and `switch` always > throws on `null`.? Whatever null opinions a construct has, these are > applied > before we even test any patterns. > > We can formalize the intuition outlined above as: type patterns that > are _total_ > on their target operand (`var x`, and `T t` on an operand of type `U`, > where `U > <: T`) match null, and non-total type patterns do not. (Another way to say > this is: a `var` pattern is the "any" pattern, and a type pattern that > is? total > on its operand type is also an "any" pattern.)? Additionally, the `null` > constant pattern matches null.? These are the _only_ nullable patterns. > > In our `Box` example, this means that the last case (whether written > as `Box(var > o)` or `Box(Object o)`) matches all boxes, including those containing null > (because the nested pattern is total on the nested operand), but the > first two > cases do not. > > If we retain the current absolute hostility of `switch` to nulls, we can't > trivially refactor from > > ``` > switch (o) { > ??? case Box(Chocolate c): > ??? case Box(Frog f): > ??? case Box(var o): > } > ``` > to > > ``` > switch (o) { > ??? case Box(var contents): > ??????? switch (contents) { > ??????????? case Chocolate c: > ??????????? case Frog f: > ??????????? case Object o: > ??????? } > ??? } > } > ``` > > because the inner `switch(contents)` would NPE before we tried to > match any of > the patterns it contains.? Instead, the user would explicitly have to > do an `if > (contents == null)` test, and, if the intent was to handle `null` in > the same > way as the `Object o` case, some duplication of code would be needed.? > We can > address this sharp corner by slightly relaxing the null-hostility of > `switch`, > as described below. > > A similar sharp corner is the decomposition of a nested pattern `P(Q)` > into > `P(alpha) & alpha instanceof Q`; while this is intended to be a > universally > valid transformation, if P's 1st component might be null and Q is > total,? this > transformation would not be valid because of the existing (mild) > null-hostility > of `instanceof`.? Again, we may be able to address this by adjusting > the rules > surrounding `instanceof` slightly. > > ## Generalizing switch > > The refactoring example above motivates why we might want to relax the > null-handling behavior of `switch`.? On the other hand, the one thing the > current behavior has going for it is that at least the current > behavior is easy > to reason about; it always throws when confronted with a `null`.? Any > relaxed > behavior would be more complex; some switches would still have to > throw (for > compatibility with existing semantics), and some (which can't be expressed > today) would accept nulls.? This is a tricky balance to achieve, but I > think we > have a found a good one. > > A starting point is that we don't want to require readers to do an _O(n)_ > analysis of each of the `case` labels just to determine whether a > given switch > accepts `null` or not; this should be an _O(1)_ analysis.? (We do not > want to > introduce a new flavor of `switch`, such as `switch-nullable`; this > might seem > to fix the proximate problem but would surely create others. As we've > done with > expression switch and patterns, we'd rather rehabilitate `switch` than > create > an almost-but-not-quite-the-same variant.) > > Let's start with the null pattern, which we'll spell for sake of > exposition > `case null`.? What if you were allowed to say `case null` in a switch, > and the > switch would do the obvious thing? > > ``` > switch (o) { > ??? case null -> System.out.println("Ugh, null"); > ??? case String s -> System.out.println("Yay, non-null: " + s); > } > ``` > > Given that the `case null` appears so close to the `switch`, it does > not seem > confusing that this switch would match `null`; the existence of `case > null` at > the top of the switch makes it pretty clear that this is intended > behavior.? (We > could further restrict the null pattern to being the first pattern in > a switch, > to make this clearer.) > > Now, let's look at the other end of the switch -- the last case.? What > if the > last pattern is a total pattern?? (Note that if any `case` has a total > pattern, > it _must_ be the last one, otherwise the cases after that would be > dead, which > would be an error.)? Is it also reasonable for that to match null?? > After all, > we're saying "everything": > > ``` > switch (o) { > ??? case String s: ... > ??? case Object o: ... > } > ``` > > Under this interpretation, the switch-refactoring anomaly above goes away. > > The direction we're going here is that if we can localize the > null-acceptance of > switches in the first (is it `case null`?) and last (is it total?) > cases, then > the incremental complexity of allowing _some_ switches to accept null > might be > outweighed by the incremental benefit of treating `null` more > uniformly (and > thus eliminating the refactoring anomalies.)? Note also that there is > no actual > code compatibility issue; this is all mental-model compatibility. > > So far, we're suggesting: > > ?- A switch with a constant `null` case? will accept nulls; > ?- If present, a constant `null` case must go first; > ?- A switch with a total (any) case matches also accepts nulls; > ?- If present, a total (any) case must go last. > > #### Relocating the problem > > It might be more helpful to view these changes as not changing the > behavior of > `switch`, but of the `default` case of `switch`.? We can equally well > interpret > the current behavior as: > > ?- `switch` always accepts `null`, but matching the `default` case of > a `switch` > ?? throws `NullPointerException`; > ?- any `switch` without a `default` case has an implicit do-nothing > `default` > ?? case. > > If we adopt this change of perspective, then `default`, not `switch`, > is in > control of the null rejection behavior -- and we can view these changes as > adjusting the behavior of `default`.? So we can recast the proposed > changes as: > > ? - Switches accept null; > ? - A constant `null` case will match nulls, and must go first; > ? - A total switch (a switch with a total `case`) cannot have a > `default` case; > ? - A non-total switch without a `default` case gets an implicit > do-nothing > ??? `default` case; > ? - Matching the (implicit or explicit) default case with a `null` operand > ??? always throws NPE. > > The main casualty here is that the `default` case does not mean the same > thing as `case var x` or `case Object o`.? We can't deprecate > `default`, but > for pattern switches, it becomes much less useful. > > #### What about method (declared) patterns? > > So far, we've declared all patterns, except the `null` constant > pattern and the > total (any) pattern, to not match `null`.? What about patterns that are > explicitly declared in code?? It turns out we can rule out these matching > `null` fairly easily. > > We can divide declared patterns into three kinds: deconstruction > patterns (dual > to constructors), static patterns (dual to static methods), and instance > patterns (dual to instance methods.)? For both deconstruction and instance > patterns, the match target becomes the receiver; method bodies are never > expected to deal with the case where `this == null`. > > For static patterns, it is conceivable that they could match `null`, > but this > would put a fairly serious burden on writers of static patterns to > check for > `null` -- which they would invariably forget, and many more NPEs would > ensue. > (Think about writing the pattern for `Optional.of(T t)` -- it would be > overwhelmingly likely we'd forget to check the target for nullity.)? > SO there > is a strong argument to simply say "declared patterns never match > null", to > not put writers of such patterns in this situation. > > So, only the top and bottom patterns in a switch could match null; if > the top > pattern is not `case null`, and the bottom pattern is not total, then > the switch > throws NPE on null, otherwise it accepts null. > > #### Adjusting instanceof > > The remaining anomaly we had was about unrolling nested patterns when > the inner > pattern is total.? We can plug this by simply outlawing total patterns in > `instanceof`. > > This may seem like a cheap trick, but it makes sense on its own.? If the > following statement was allowed: > > ``` > if (e instanceof var x) { X } > ``` > > it would simply be confusing; on the one hand, it looks like it should > always > match, but on the other, `instanceof` is historically null-hostile.? > And, if the > pattern always matches, then the `if` statement is silly; it should be > replaced > with: > > ``` > var x = e; > X > ``` > > since there's nothing conditional about it.? So by banning "any" > patterns on the > RHS of `instanceof`, we both avoid a confusion about what is going to > happen, > and we prevent the unrolling anomaly. > > For reasons of compatibility, we will have to continue to allow > > ``` > if (e instanceof Object) { ... } > ``` > > which succeeds on all non-null operands. > > We've been a little sloppy with the terminology of "any" vs "total"; > note that > in > > ``` > Point p; > if (p instanceof Point(var x, var y)) { } > ``` > > the pattern `Point(var x, var y)` is total on `Point`, but not an > "any" pattern > -- it still doesn't match on p == null. > > On the theory that an "any" pattern in `instanceof` is silly, we may > also want > to ban other "silly" patterns in `instanceof`, such as constant > patterns, since > all of the following have simpler forms: > > ``` > if (x instanceof null) { ... } > if (x instanceof "") { ... } > if (i instanceof 3) { ... } > ``` > > In the first round (type patterns in `instanceof`), we mostly didn't > confront > this issue, saying that `instanceof T t` matched in all the cases where > `instanceof T` would match.? But given that the solution for `switch` > relies > on "any" patterns matching null, we may wish to adjust the behavior of > `instanceof` before it exits preview. > > > [jep305]: https://openjdk.java.net/jeps/305 > [patternmatch]: pattern-match.html > From forax at univ-mlv.fr Thu Jul 23 18:38:12 2020 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 23 Jul 2020 18:38:12 +0000 Subject: Next up for patterns: type patterns in switch In-Reply-To: <8a0d00c7-07a4-2d1e-8570-7e2cebd7f00e@oracle.com> References: <8a0d00c7-07a4-2d1e-8570-7e2cebd7f00e@oracle.com> Message-ID: Right now: add only the type Pattern and the null Pattern so people can test and report if we go in the right direction or not. Don't do deconstruction now ! We are not ready :) In next releases? Add constant Pattern and static Pattern. Ban instance pattern because you don't want to match something that depends on a mutable states (my worst experience with Scala). On guards, they do not work well with exhaustiveness. Still not sure they are useful. On nullity, I prefer the total pattern to be explicit instead of implicit (given we have var, the type on the expression taken by the switch can be fuzzy for someone doing a quick look to the code), case any x, case _ x or whatever the syntax is, is more readable IMO. And I agree on banning total patterns in instanceof. Remi On July 23, 2020 4:20:01 PM UTC, Brian Goetz wrote: >In case I wasn't clear, this was a proposal to proceed on _right now_.? > >There's been no comments so far; should I take that as "perfect, ship >it?" > > >On 6/24/2020 10:44 AM, Brian Goetz wrote: >> There are a lot of directions we could take next for pattern >> matching.? The one that builds most on what we've already done, and >> offers significant incremental expressiveness, is extending the type >> patterns we already have to a new context: switch.? (There is still >> plenty of work to do on deconstruction patterns, pattern assignment, >> etc, but these require more design work.) >> >> Here's an overview of where I think we are here. >> >> [JEP 305][jep305] introduced the first phase of [pattern >> matching][patternmatch] >> into the Java language.? It was deliberately limited, focusing on >only >> one kind >> of pattern (type test patterns) and one linguistic context >(`instanceof`). >> Having introduced the concept to Java developers, we can now extend >> both the >> kinds of patterns and the linguistic context where patterns are used. >> >> ## Patterns in switch >> >> The obvious next context in which to introduce pattern matching is >> `switch`;? a >> switch using patterns as `case` labels can replace `if .. else if` >> chains with >> a more direct way of expressing a multi-way conditional. >> >> Unfortunately, `switch` is one of the most complex, irregular >> constructs we have >> in Java, so we must teach it some new tricks while avoiding some >> existing traps. >> Such tricks and traps may include: >> >> **Typing.**? Currently, the operand of a `switch` may only be one of >the >> integral primitive types, the box type of an integral primitive, >> `String`, or an >> `enum` type.? (Further, if the `switch` operand is an `enum` type, >the >> `case` >> labels must be _unqualified_ enum constant names.)? Clearly we can >> relax this >> restriction to allow other types, and constrain the case labels to >only be >> patterns that are applicable to that type, but it may leave a seam of > >> "legacy" >> vs "pattern" switch, especially if we do not adopt bare constant >> literals as >> the denotation of constant patterns.? (We have confronted this issue >> before with >> expression switch, and concluded that it was better to rehabilitate >> the `switch` >> we have rather than create a new construct, and we will make the same > >> choice >> here, but the cost of this is often a visible seam.) >> >> **Parsing.**? The grammar currently specifies that the operand of a >> `case` label >> is a `CaseConstant`, which casts a wide syntactic net, later narrowed >with >> post-checks after attribution.? This means that, since parsing is >done >> before we >> know the type of the operand, we must be watchful for ambiguities >between >> patterns and expressions (and possibly refine the production for >> `case` labels.) >> >> **Nullity.**? The `switch` construct is currently hostile to `null`, >> but some >> patterns do match `null`, and it may be desirable if nulls can be >handled >> within a suitably crafted `switch`. >> >> **Exhaustiveness.**? For switches over the permitted subtypes of >> sealed types, >> we will want to be able to do exhaustiveness analysis -- including >for >> nested >> patterns (i.e., if `Shape`? is `Circle` or `Rect`, then `Box(Circle >> c)` and >> `Box(Rect r)` are exhaustive on `Box`.) >> >> **Fallthrough.**? Fallthrough is everyone's least favorite feature of > >> `switch`, >> but it exists for a reason.? (The mistake was making fallthrough the >> default >> behavior, but that ship has sailed.)? In the absence of an OR pattern >> combinator, one might find fallthrough in switch useful in >conjunction >> with >> patterns: >> >> ``` >> case Box(int x): >> case Bag(int x): >> ??? // use x >> ``` >> >> However, it is likely that we will, at least initially, disallow >> falling out >> of, or into, a case label with binding variables. >> >> #### Translation >> >> Switches on primitives and their wrapper types are translated using >the >> `tableswitch` or `lookupswitch` bytecodes; switches on strings and >> enums are >> lowered in the compiler to switches involving hash codes (for >strings) or >> ordinals (for enums.) >> >> For switches on patterns, we would need a new strategy, one likely >> built on >> `invokedynamic`, where we lower the cases to a densely numbered `int` > >> switch, >> and then invoke a classifier function with the operand which tells us > >> the first >> case number it matches.? So a switch like: >> >> ``` >> switch (o) { >> ??? case P: A >> ??? case Q: B >> } >> ``` >> >> is lowered to: >> >> ``` >> int target = indy[BSM=PatternSwitch, args=[P,Q]](o) >> switch (target) { >> ??? case 0: A >> ??? case 1: B >> } >> ``` >> >> A symbolic description of the patterns is provided as the bootstrap >> argument >> list, which builds a decision tree based on analysis of the patterns >> and their >> target types. >> >> #### Guards >> >> No matter how rich our patterns are, it is often the case that we >will >> want >> to provide additional filtering on the results of a pattern: >> >> ``` >> if (shape instanceof Cylinder c && c.color() == RED) { ... } >> ``` >> >> Because we use `instanceof` as part of a boolean expression, it is >easy to >> narrow the results by conjoining additional checks with `&&`.? But in > >> a `case` >> label, we do not necessarily have this opportunity.? Worse, the >> semantics of >> `switch` mean that once a `case` label is selected, there is no way >to say >> "oops, forget it, keep trying from the next label". >> >> It is common in languages with pattern matching to support some form >> of "guard" >> expression, which is a boolean expression that conditions whether the >case >> matches, such as: >> >> ``` >> case Point(var x, var y) >> ??? __where x == y: ... >> ``` >> >> Bindings from the pattern would have to be available in guard >expressions. >> >> Syntactic options (and hazards) for guards abound; users would >> probably find it >> natural to reuse `&&` to attach guards to patterns; C# has chosen >> `when` for >> introducing guards; we could use `case P if (e)`, etc. Whatever we do > >> here, >> there is a readability risk,? as the more complex guards are, the >> harder it is >> to tell where the case label ends and the "body" begins.? (And worse >> if we allow >> switch expressions inside guards.) >> >> An alternate to guards is to allow an imperative `continue` statement >in >> `switch`, which would mean "keep trying to match from the next >> label."? Given >> the existing semantics of `continue`, this is a natural extension, >but >> since >> `continue` does not currently have meaning for switch, some work >would >> have to >> be done to disambiguate continue statements in switches enclosed in >> loops.? The >> imperative version is strictly more expressive than most reasonable >> forms of the >> declarative version, but users are likely to prefer the declarative >> version. >> >> ## Nulls >> >> Almost no language design exercise is complete without some degree of > >> wrestling >> with `null`.? As we define more complex patterns than simple type >> patterns, and >> extend constructs such as `switch` (which have existing opinions >about >> nullity) >> to support patterns, we need to have a clear understanding of which >> patterns >> are nullable, and separate the nullity behaviors of patterns from the > >> nullity >> behaviors of those constructs which use patterns. >> >> ## Nullity and patterns >> >> This topic has a number of easily-tangled concerns: >> >> ?- **Construct nullability.**? Constructs to which we want to add >pattern >> ?? awareness (`instanceof`, `switch`) already have their own opinion >about >> ?? nulls.? Currently, `instanceof` always says false when presented >with a >> ?? `null`, and `switch` always NPEs.? We may, or may not, wish to >> refine these >> ?? rules in some cases. >> ?- **Pattern nullability.**? Some patterns clearly would never match >> `null` >> ?? (such as deconstruction patterns), whereas others (an "any" >> pattern, and >> ?? surely the `null` constant pattern) might make sense to match >null. >> ?- **Refactoring friendliness.**? There are a number of cases that we > >> would like >> ?? to freely refactor back and forth, such as certain chains of `if >> ... else if` >> ?? with switches. >> ?- **Nesting vs top-level.**? The "obvious" thing to do at the top >> level of a >> ?? construct is not always the "obvious" thing to do in a nested >> construct. >> ?- **Totality vs partiality.**? When a pattern is partial on the >> operand type >> ?? (e.g., `case String` when the operand of switch is `Object`), it >is >> almost >> ?? never the case we want to match null (except in the case of the >`null` >> ?? constant pattern), whereas when a pattern is total on the operand >> type (e.g., >> ?? `case Object` in the same example), it is more justifiable to >match >> null. >> ?- **Inference.**? It would be nice if a `var` pattern were simply >> inference for >> ?? a type pattern, rather than some possibly-non-denotable union. >> >> As a starting example, consider: >> >> ``` >> record Box(Object o) { } >> >> Box box = ... >> switch (box) { >> ??? case Box(Chocolate c): >> ??? case Box(Frog f): >> ??? case Box(var o): >> } >> ``` >> >> It would be highly confusing and error-prone for either of the first >two >> patterns to match `Box(null)` -- given that `Chocolate` and `Frog` >> have no type >> relation, it should be perfectly safe to reorder the two. But, >because >> the last >> pattern seems so obviously total on boxes, it is quite likely that >> what the >> author wants is to match all remaining boxes, including those that >> contain null. >> (Further, it would be terrible if there were _no_ way to say "Match >> any `Box`, >> even if it contains `null`.? (While one might initially think this >> could be >> repaired with OR patterns, imagine that `Box` had _n_ components -- >> we'd need to >> OR together _2^n_ patterns, with complex merging, to express all the >> possible >> combinations of nullity.)) >> >> Scala and C# took the approach of saying that "var" patterns are not >> just type >> inference, they are "any" patterns -- so `Box(Object o)` matches >boxes >> containing a non-null payload, where `Box(var o)` matches all boxes.? >This >> means, unfortunately, that `var` is not mere type inference -- which >> complicates >> the role of `var` in the language considerably.? Users should not >have >> to choose >> between the semantics they want and being explicit about types; these > >> should be >> orthogonal choices.? The above `switch` should be equivalent to: >> >> ``` >> Box box = ... >> switch (box) { >> ??? case Box(Chocolate c): >> ??? case Box(Frog f): >> ??? case Box(Object o): >> } >> ``` >> >> and the choice to use `Object` or `var` should be solely one of >> whether the >> manifest types are deemed to improve or impair readability. >> >> #### Construct and pattern nullability >> >> Currently, `instanceof` always says `false` on `null`, and `switch` >always >> throws on `null`.? Whatever null opinions a construct has, these are >> applied >> before we even test any patterns. >> >> We can formalize the intuition outlined above as: type patterns that >> are _total_ >> on their target operand (`var x`, and `T t` on an operand of type >`U`, >> where `U >> <: T`) match null, and non-total type patterns do not. (Another way >to say >> this is: a `var` pattern is the "any" pattern, and a type pattern >that >> is? total >> on its operand type is also an "any" pattern.)? Additionally, the >`null` >> constant pattern matches null.? These are the _only_ nullable >patterns. >> >> In our `Box` example, this means that the last case (whether written >> as `Box(var >> o)` or `Box(Object o)`) matches all boxes, including those containing >null >> (because the nested pattern is total on the nested operand), but the >> first two >> cases do not. >> >> If we retain the current absolute hostility of `switch` to nulls, we >can't >> trivially refactor from >> >> ``` >> switch (o) { >> ??? case Box(Chocolate c): >> ??? case Box(Frog f): >> ??? case Box(var o): >> } >> ``` >> to >> >> ``` >> switch (o) { >> ??? case Box(var contents): >> ??????? switch (contents) { >> ??????????? case Chocolate c: >> ??????????? case Frog f: >> ??????????? case Object o: >> ??????? } >> ??? } >> } >> ``` >> >> because the inner `switch(contents)` would NPE before we tried to >> match any of >> the patterns it contains.? Instead, the user would explicitly have to > >> do an `if >> (contents == null)` test, and, if the intent was to handle `null` in >> the same >> way as the `Object o` case, some duplication of code would be >needed.? >> We can >> address this sharp corner by slightly relaxing the null-hostility of >> `switch`, >> as described below. >> >> A similar sharp corner is the decomposition of a nested pattern >`P(Q)` >> into >> `P(alpha) & alpha instanceof Q`; while this is intended to be a >> universally >> valid transformation, if P's 1st component might be null and Q is >> total,? this >> transformation would not be valid because of the existing (mild) >> null-hostility >> of `instanceof`.? Again, we may be able to address this by adjusting >> the rules >> surrounding `instanceof` slightly. >> >> ## Generalizing switch >> >> The refactoring example above motivates why we might want to relax >the >> null-handling behavior of `switch`.? On the other hand, the one thing >the >> current behavior has going for it is that at least the current >> behavior is easy >> to reason about; it always throws when confronted with a `null`.? Any > >> relaxed >> behavior would be more complex; some switches would still have to >> throw (for >> compatibility with existing semantics), and some (which can't be >expressed >> today) would accept nulls.? This is a tricky balance to achieve, but >I >> think we >> have a found a good one. >> >> A starting point is that we don't want to require readers to do an >_O(n)_ >> analysis of each of the `case` labels just to determine whether a >> given switch >> accepts `null` or not; this should be an _O(1)_ analysis.? (We do not > >> want to >> introduce a new flavor of `switch`, such as `switch-nullable`; this >> might seem >> to fix the proximate problem but would surely create others. As we've > >> done with >> expression switch and patterns, we'd rather rehabilitate `switch` >than >> create >> an almost-but-not-quite-the-same variant.) >> >> Let's start with the null pattern, which we'll spell for sake of >> exposition >> `case null`.? What if you were allowed to say `case null` in a >switch, >> and the >> switch would do the obvious thing? >> >> ``` >> switch (o) { >> ??? case null -> System.out.println("Ugh, null"); >> ??? case String s -> System.out.println("Yay, non-null: " + s); >> } >> ``` >> >> Given that the `case null` appears so close to the `switch`, it does >> not seem >> confusing that this switch would match `null`; the existence of `case > >> null` at >> the top of the switch makes it pretty clear that this is intended >> behavior.? (We >> could further restrict the null pattern to being the first pattern in > >> a switch, >> to make this clearer.) >> >> Now, let's look at the other end of the switch -- the last case.? >What >> if the >> last pattern is a total pattern?? (Note that if any `case` has a >total >> pattern, >> it _must_ be the last one, otherwise the cases after that would be >> dead, which >> would be an error.)? Is it also reasonable for that to match null?? >> After all, >> we're saying "everything": >> >> ``` >> switch (o) { >> ??? case String s: ... >> ??? case Object o: ... >> } >> ``` >> >> Under this interpretation, the switch-refactoring anomaly above goes >away. >> >> The direction we're going here is that if we can localize the >> null-acceptance of >> switches in the first (is it `case null`?) and last (is it total?) >> cases, then >> the incremental complexity of allowing _some_ switches to accept null > >> might be >> outweighed by the incremental benefit of treating `null` more >> uniformly (and >> thus eliminating the refactoring anomalies.)? Note also that there is > >> no actual >> code compatibility issue; this is all mental-model compatibility. >> >> So far, we're suggesting: >> >> ?- A switch with a constant `null` case? will accept nulls; >> ?- If present, a constant `null` case must go first; >> ?- A switch with a total (any) case matches also accepts nulls; >> ?- If present, a total (any) case must go last. >> >> #### Relocating the problem >> >> It might be more helpful to view these changes as not changing the >> behavior of >> `switch`, but of the `default` case of `switch`.? We can equally well > >> interpret >> the current behavior as: >> >> ?- `switch` always accepts `null`, but matching the `default` case of > >> a `switch` >> ?? throws `NullPointerException`; >> ?- any `switch` without a `default` case has an implicit do-nothing >> `default` >> ?? case. >> >> If we adopt this change of perspective, then `default`, not `switch`, > >> is in >> control of the null rejection behavior -- and we can view these >changes as >> adjusting the behavior of `default`.? So we can recast the proposed >> changes as: >> >> ? - Switches accept null; >> ? - A constant `null` case will match nulls, and must go first; >> ? - A total switch (a switch with a total `case`) cannot have a >> `default` case; >> ? - A non-total switch without a `default` case gets an implicit >> do-nothing >> ??? `default` case; >> ? - Matching the (implicit or explicit) default case with a `null` >operand >> ??? always throws NPE. >> >> The main casualty here is that the `default` case does not mean the >same >> thing as `case var x` or `case Object o`.? We can't deprecate >> `default`, but >> for pattern switches, it becomes much less useful. >> >> #### What about method (declared) patterns? >> >> So far, we've declared all patterns, except the `null` constant >> pattern and the >> total (any) pattern, to not match `null`.? What about patterns that >are >> explicitly declared in code?? It turns out we can rule out these >matching >> `null` fairly easily. >> >> We can divide declared patterns into three kinds: deconstruction >> patterns (dual >> to constructors), static patterns (dual to static methods), and >instance >> patterns (dual to instance methods.)? For both deconstruction and >instance >> patterns, the match target becomes the receiver; method bodies are >never >> expected to deal with the case where `this == null`. >> >> For static patterns, it is conceivable that they could match `null`, >> but this >> would put a fairly serious burden on writers of static patterns to >> check for >> `null` -- which they would invariably forget, and many more NPEs >would >> ensue. >> (Think about writing the pattern for `Optional.of(T t)` -- it would >be >> overwhelmingly likely we'd forget to check the target for nullity.)? >> SO there >> is a strong argument to simply say "declared patterns never match >> null", to >> not put writers of such patterns in this situation. >> >> So, only the top and bottom patterns in a switch could match null; if > >> the top >> pattern is not `case null`, and the bottom pattern is not total, then > >> the switch >> throws NPE on null, otherwise it accepts null. >> >> #### Adjusting instanceof >> >> The remaining anomaly we had was about unrolling nested patterns when > >> the inner >> pattern is total.? We can plug this by simply outlawing total >patterns in >> `instanceof`. >> >> This may seem like a cheap trick, but it makes sense on its own.? If >the >> following statement was allowed: >> >> ``` >> if (e instanceof var x) { X } >> ``` >> >> it would simply be confusing; on the one hand, it looks like it >should >> always >> match, but on the other, `instanceof` is historically null-hostile.? >> And, if the >> pattern always matches, then the `if` statement is silly; it should >be >> replaced >> with: >> >> ``` >> var x = e; >> X >> ``` >> >> since there's nothing conditional about it.? So by banning "any" >> patterns on the >> RHS of `instanceof`, we both avoid a confusion about what is going to > >> happen, >> and we prevent the unrolling anomaly. >> >> For reasons of compatibility, we will have to continue to allow >> >> ``` >> if (e instanceof Object) { ... } >> ``` >> >> which succeeds on all non-null operands. >> >> We've been a little sloppy with the terminology of "any" vs "total"; >> note that >> in >> >> ``` >> Point p; >> if (p instanceof Point(var x, var y)) { } >> ``` >> >> the pattern `Point(var x, var y)` is total on `Point`, but not an >> "any" pattern >> -- it still doesn't match on p == null. >> >> On the theory that an "any" pattern in `instanceof` is silly, we may >> also want >> to ban other "silly" patterns in `instanceof`, such as constant >> patterns, since >> all of the following have simpler forms: >> >> ``` >> if (x instanceof null) { ... } >> if (x instanceof "") { ... } >> if (i instanceof 3) { ... } >> ``` >> >> In the first round (type patterns in `instanceof`), we mostly didn't >> confront >> this issue, saying that `instanceof T t` matched in all the cases >where >> `instanceof T` would match.? But given that the solution for `switch` > >> relies >> on "any" patterns matching null, we may wish to adjust the behavior >of >> `instanceof` before it exits preview. >> >> >> [jep305]: https://openjdk.java.net/jeps/305 >> [patternmatch]: pattern-match.html >> -- Envoy? de mon appareil Android avec Courriel K-9 Mail. Veuillez excuser ma bri?vet?. From brian.goetz at oracle.com Thu Jul 23 18:52:50 2020 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 23 Jul 2020 14:52:50 -0400 Subject: Next up for patterns: type patterns in switch In-Reply-To: References: <8a0d00c7-07a4-2d1e-8570-7e2cebd7f00e@oracle.com> Message-ID: On 7/23/2020 2:38 PM, Remi Forax wrote: > Don't do deconstruction now ! We are not ready :) Now I'm confused.? Didn't I say exactly that in the first paragraph? > On guards, they do not work well with exhaustiveness. Still not sure > they are useful. It works fine, it's just more work for the user to get right. We induce a domination ordering on patterns.? If `T <: U`, then `T t` < `U u` (`T t` is less specific than `U u`.)? Similarly, for all guard conditions g, `P & g` < `P`.? What this says is that if you want exhaustiveness, you need an unguarded pattern somewhere, either: ??? case A: ??? case B & g: ??? case B:????????????? // catches B & !g ??? case C: or ??? case A: ??? case B & g: ??? case C: ??? case Object: ?? // catches B & !g I understand your diffidence about guards, but I'm not sure we can do nothing.? The main reason that some sort of guards feel like a forced move (could be an imperative guard, like `continue`, but I don't think anyone would be happy with that) is that the fall-off-the-cliff behavior is so bad.? If you have a 26-arm switch, and you want the equivalent of the second of the above cases -- B-but-not-g gets shunted into the bottom clause -- you may very well have to refactor away from switch, or at least mangle your switch badly, which would be pretty bad. > On nullity, I prefer the total pattern to be explicit instead of > implicit (given we have var, the type on the expression taken by the > switch can be fuzzy for someone doing a quick look to the code), case > any x, case _ x or whatever the syntax is, is more readable IMO. It is unfortunate that `var x` is more fuzzy about types, but less fuzzy about totality (`var x` is always total.)? It is also unfortunate that `default` can't be our "any" clause.?? Not sure that introducing a third thing is helpful, though. From mark at io7m.com Thu Jul 23 20:34:30 2020 From: mark at io7m.com (Mark Raynsford) Date: Thu, 23 Jul 2020 20:34:30 +0000 Subject: Next up for patterns: type patterns in switch In-Reply-To: <8a0d00c7-07a4-2d1e-8570-7e2cebd7f00e@oracle.com> References: <8a0d00c7-07a4-2d1e-8570-7e2cebd7f00e@oracle.com> Message-ID: <20200723203430.3e6ced1f@sunflower.int.arc7.info> On 2020-07-23T12:20:01 -0400 Brian Goetz wrote: > In case I wasn't clear, this was a proposal to proceed on _right now_.? > There's been no comments so far; should I take that as "perfect, ship it?" Apologies for my lack of participation in these lists lately. I've been inundated with non-JDK work over the past six months or so. That said, I've read this proposal multiple times and I can see nothing to which to object. Best to interpret my silence as agreement. :) -- Mark Raynsford | https://www.io7m.com From john.r.rose at oracle.com Thu Jul 23 20:55:35 2020 From: john.r.rose at oracle.com (John Rose) Date: Thu, 23 Jul 2020 13:55:35 -0700 Subject: Next up for patterns: type patterns in switch In-Reply-To: References: Message-ID: Just one comment here: This is a masterful summary of many months work pursuing hard-won insights; thank you; let?s do this. ? John P.S. Well, not exactly. You didn?t expect *no* comment from me? :-) It is slightly premature to completely outlaw `x instanceof 42`, because of nulls: You can replace that with `x == 42` only if `x` is `int`. With strings, identity is also a problem; `x instanceof "foo"` does not replace with an `==` expression. In the end, if we outlaw `x instanceof 42` the workaround might be `Objects.equals(x,42)` but that incurs boxing overhead if `x` happens to be a primitive. So, I think the fate of `EXPR instanceof CON_PAT` is up for grabs. That said, I?m fine with leaving it out for starters; it can be added after further thought?or not. From john.r.rose at oracle.com Thu Jul 23 21:05:08 2020 From: john.r.rose at oracle.com (John Rose) Date: Thu, 23 Jul 2020 14:05:08 -0700 Subject: Next up for patterns: type patterns in switch In-Reply-To: References: <8a0d00c7-07a4-2d1e-8570-7e2cebd7f00e@oracle.com> Message-ID: On Jul 23, 2020, at 11:38 AM, Remi Forax wrote: > > In next releases? Add constant Pattern and static Pattern. Ban instance pattern because you don't want to match something that depends on a mutable states (my worst experience with Scala). Do you mean mutable states in some sort of pattern token (which is not AFAIK on the table) or mutable states in the object-being-tested or mutable states in a pattern parameter (e.g., an object being compared to the object-being-tested)? In the latter two cases, the mutability bug is that you test an object and it passes a test on Tuesday but not today, either because the object changed, or the *other* object hanged. I see there are horrible bugs there, but for Java that ship is already sailed. Object::equals/hashCode can depend on mutable state; equals can depend on mutable state in the second operand. Programmers just have to deal with it, or choose to use immutables (inlines! records!). Patterns amount to a more generous slice of the functionalities which Object::equals/hashCode also serve. If we try to ?tighten up? some aspect of patterns to avoid mutability bugs, we?ll just force users to drop down to Object::equals, for no strong reason IMO. Banning instance patterns (while allowing static patterns) just puts in a cardboard wall that makes it slightly harder to code some bugs but will greatly annoy legitimate designers and users. (Or do I misunderstand your point?) ? John From forax at univ-mlv.fr Thu Jul 23 21:53:12 2020 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 23 Jul 2020 21:53:12 +0000 Subject: Next up for patterns: type patterns in switch In-Reply-To: References: <8a0d00c7-07a4-2d1e-8570-7e2cebd7f00e@oracle.com> Message-ID: <0567C5FB-DCF6-49B1-8E1A-47CB158C3A5D@univ-mlv.fr> On July 23, 2020 6:52:50 PM UTC, Brian Goetz wrote: > > >On 7/23/2020 2:38 PM, Remi Forax wrote: >> Don't do deconstruction now ! We are not ready :) > >Now I'm confused.? Didn't I say exactly that in the first paragraph? Yes, we agree. > >> On guards, they do not work well with exhaustiveness. Still not sure >> they are useful. > >It works fine, it's just more work for the user to get right. > >We induce a domination ordering on patterns.? If `T <: U`, then `T t` < > >`U u` (`T t` is less specific than `U u`.)? Similarly, for all guard >conditions g, `P & g` < `P`.? What this says is that if you want >exhaustiveness, you need an unguarded pattern somewhere, either: > > ??? case A: > ??? case B & g: > ??? case B:????????????? // catches B & !g > ??? case C: > >or > > ??? case A: > ??? case B & g: > ??? case C: > ??? case Object: ?? // catches B & !g > >I understand your diffidence about guards, but I'm not sure we can do >nothing.? The main reason that some sort of guards feel like a forced >move (could be an imperative guard, like `continue`, but I don't think >anyone would be happy with that) is that the fall-off-the-cliff >behavior >is so bad.? If you have a 26-arm switch, and you want the equivalent of > >the second of the above cases -- B-but-not-g gets shunted into the >bottom clause -- you may very well have to refactor away from switch, >or >at least mangle your switch badly, which would be pretty bad. I am worry about the whole complexity. What about guard clauses with an order switch(a) { case Object & a instanceof A -> case Object & a instanceof B -> } Can the two cases be swapped ? And the size of the JLS section that will explain how to compute the exhaustiveness. By example switch(p) { case P & v >= 0 -> case P & v < 0 -> } Can be ok or not depending if v is an int or a double. or switch(opt) { case Optional & opt.isPresent() -> case Optional & opt.isEmpty() -> } You will have to draw a line somewhere and because Java uses a lot of abstraction in libraries collection, optional, etc you may have to go pretty far. > >> On nullity, I prefer the total pattern to be explicit instead of >> implicit (given we have var, the type on the expression taken by the >> switch can be fuzzy for someone doing a quick look to the code), case > >> any x, case _ x or whatever the syntax is, is more readable IMO. > >It is unfortunate that `var x` is more fuzzy about types, but less >fuzzy >about totality (`var x` is always total.)? It is also unfortunate that >`default` can't be our "any" clause.?? Not sure that introducing a >third thing is helpful, though. var x and default are not on the same plane. So it's not really a third thing. We are introducing something special for the bottom, null, but not for the top ? Remi -- On mobile From forax at univ-mlv.fr Thu Jul 23 22:15:31 2020 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 23 Jul 2020 22:15:31 +0000 Subject: Next up for patterns: type patterns in switch In-Reply-To: References: <8a0d00c7-07a4-2d1e-8570-7e2cebd7f00e@oracle.com> Message-ID: On July 23, 2020 9:05:08 PM UTC, John Rose wrote: >On Jul 23, 2020, at 11:38 AM, Remi Forax wrote: >> >> In next releases? Add constant Pattern and static Pattern. Ban >instance pattern because you don't want to match something that depends >on a mutable states (my worst experience with Scala). > >Do you mean mutable states in some sort of pattern token (which is >not AFAIK on the table) or mutable states in the object-being-tested >or mutable states in a pattern parameter (e.g., an object being >compared >to the object-being-tested)? In the latter two cases, the mutability >bug >is that you test an object and it passes a test on Tuesday but not >today, >either because the object changed, or the *other* object hanged. > >I see there are horrible bugs there, but for Java that ship is already >sailed. >Object::equals/hashCode can depend on mutable state; equals can depend >on mutable state in the second operand. Programmers just have to deal >with it, or choose to use immutables (inlines! records!). It's not because there are some loaded guns here and there that we should not try to avoid to add more. > >Patterns amount to a more generous slice of the functionalities >which Object::equals/hashCode also serve. If we try to ?tighten up? >some aspect of patterns to avoid mutability bugs, we?ll just force >users to drop down to Object::equals, for no strong reason IMO. I hate to have to run my app only on Tuesday is a good reason. The current switch is limited to non mutable class is another good reason and forcing users to explicitly use equals when something is mutable is not that bad too, ok it's less magical. But being able to understand the code is still more important that writing it. > >Banning instance patterns (while allowing static patterns) just puts >in a cardboard wall that makes it slightly harder to code some bugs >but will greatly annoy legitimate designers and users. As you said records and inlines are ok, Why not restricting instance patterns to them. > >(Or do I misunderstand your point?) Nope :) I want at least to be sure we have tried to corral mutable objects. > >? John Remi -- On mobile From brian.goetz at oracle.com Thu Jul 23 22:48:04 2020 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 23 Jul 2020 18:48:04 -0400 Subject: Next up for patterns: type patterns in switch In-Reply-To: References: Message-ID: <1968af71-40c1-445a-ccc1-b7512122dc46@oracle.com> > P.S. Well, not exactly. You didn?t expect *no* comment from me? :-) There's always one .... > It is slightly premature to completely outlaw `x instanceof 42`, > because of nulls: You can replace that with `x == 42` only if `x` > is `int`. With strings, identity is also a problem; `x instanceof "foo"` > does not replace with an `==` expression. In the end, if we outlaw > `x instanceof 42` the workaround might be `Objects.equals(x,42)` > but that incurs boxing overhead if `x` happens to be a primitive. > So, I think the fate of `EXPR instanceof CON_PAT` is up for grabs. > That said, I?m fine with leaving it out for starters; it can be added > after further thought?or not. Truth be told, I am hoping we can avoid doing constant patterns entirely.? The obvious denotation (a literal) leads to at least the appearance of ambiguity -- when a user looks at `foo(0)`, they can't immediately tell whether the 0 is a parameter or a nested pattern (and worse when patterns have parameters.) In instanceof, we can avoid them entirely by unrolling into ??? if (x instanceof Foo(var y) && y == 0) { ... } which is more flexible anyway because we can have not only equality constraints, but comparison constraints, etc.? If we have guards, we can do the same with cases: ??? case Foo(var y) where y == 0 So it's not clear whether we need constant patterns _at all_, except as far as it looks like existing switches are full of constant patterns.? But maybe that's really just a funny form of case label.... From john.r.rose at oracle.com Fri Jul 24 01:17:14 2020 From: john.r.rose at oracle.com (John Rose) Date: Thu, 23 Jul 2020 18:17:14 -0700 Subject: Next up for patterns: type patterns in switch In-Reply-To: <1968af71-40c1-445a-ccc1-b7512122dc46@oracle.com> References: <1968af71-40c1-445a-ccc1-b7512122dc46@oracle.com> Message-ID: <9A4CE546-D715-41F9-B8CC-46E66AF90947@oracle.com> On Jul 23, 2020, at 3:48 PM, Brian Goetz wrote: > >> P.S. Well, not exactly. You didn?t expect *no* comment from me? :-) > > There's always one .... > >> It is slightly premature to completely outlaw `x instanceof 42`, >> because of nulls: You can replace that with `x == 42` only if `x` >> is `int`. With strings, identity is also a problem; `x instanceof "foo"` >> does not replace with an `==` expression. In the end, if we outlaw >> `x instanceof 42` the workaround might be `Objects.equals(x,42)` >> but that incurs boxing overhead if `x` happens to be a primitive. >> So, I think the fate of `EXPR instanceof CON_PAT` is up for grabs. >> That said, I?m fine with leaving it out for starters; it can be added >> after further thought?or not. > > Truth be told, I am hoping we can avoid doing constant patterns entirely. The obvious denotation (a literal) leads to at least the appearance of ambiguity -- when a user looks at `foo(0)`, they can't immediately tell whether the 0 is a parameter or a nested pattern (and worse when patterns have parameters.) > > In instanceof, we can avoid them entirely by unrolling into > > if (x instanceof Foo(var y) && y == 0) { ... } > > which is more flexible anyway because we can have not only equality constraints, but comparison constraints, etc. If we have guards, we can do the same with cases: > > case Foo(var y) where y == 0 > > So it's not clear whether we need constant patterns _at all_, except as far as it looks like existing switches are full of constant patterns. But maybe that's really just a funny form of case label?. Sure, but that wasn?t my point. You can?t really say ?y == 0? uniformly, so it?s a trap to appeal to that nice looking workaround. You probably have to say ?Objects.equal(y, 0)? to get the equivalent to what we are considering with constant patterns. Here?s the example I was trying to evoke: Integer x = flipcoin() ? null : 42; if (x == 42) ; // throws NPE 50% of the time case (x) { // never throws case null: ; case 42: break; } if (x instanceof 42) ; // never throws From john.r.rose at oracle.com Fri Jul 24 01:20:20 2020 From: john.r.rose at oracle.com (John Rose) Date: Thu, 23 Jul 2020 18:20:20 -0700 Subject: Next up for patterns: type patterns in switch In-Reply-To: <0567C5FB-DCF6-49B1-8E1A-47CB158C3A5D@univ-mlv.fr> References: <8a0d00c7-07a4-2d1e-8570-7e2cebd7f00e@oracle.com> <0567C5FB-DCF6-49B1-8E1A-47CB158C3A5D@univ-mlv.fr> Message-ID: <6A81A458-9FA8-43D3-9D81-6EB7E2D61CA1@oracle.com> On Jul 23, 2020, at 2:53 PM, Remi Forax wrote: > > var x and default are not on the same plane. So it's not really a third thing. > We are introducing something special for the bottom, null, but not for the top ? Eh; null doesn?t need to be that special, but Brian?s point is that you can just mandate that it appears at the top or nowhere. If you don?t mandate that, then type-coverage checks ensure that a ?case null? which appears after a nullable case (a total one) will be not-reachable, and a static error. From john.r.rose at oracle.com Fri Jul 24 01:21:16 2020 From: john.r.rose at oracle.com (John Rose) Date: Thu, 23 Jul 2020 18:21:16 -0700 Subject: Next up for patterns: type patterns in switch In-Reply-To: References: <8a0d00c7-07a4-2d1e-8570-7e2cebd7f00e@oracle.com> Message-ID: <799DEB0F-A8ED-483E-9159-0407E9EC1DB1@oracle.com> On Jul 23, 2020, at 3:15 PM, Remi Forax wrote: > > I want at least to be sure we have tried to corral mutable objects. The horse has left the corral. From amaembo at gmail.com Fri Jul 24 03:55:13 2020 From: amaembo at gmail.com (Tagir Valeev) Date: Fri, 24 Jul 2020 10:55:13 +0700 Subject: [records] Is C-style array declaration in components dropped? Message-ID: Hello! The JLS 14 record preview spec allows C-style array declaration in components (like record R(int x[])) [1]: RecordComponent: { VariableModifier } UnannType VariableDeclaratorId VariableArityRecordComponent VariableDeclaratorId: Identifier [ Dims ] However, it appears that JLS 15 draft spec doesn't allow it anymore [2] RecordComponent: { Annotation } UnannType Identifier VariableArityRecordComponent This change is not listed in the draft spec prolog in "The changes are the same as those in the first preview of Records in Java SE 14, except for the following", so I overlooked it when updated the records support in IntelliJ IDEA. Is this intended change? If yes, then probably the changes section should be updated to include it as well. I must say that I heavily support this change. Supporting C-style array declaration in records adds complexity in many places of our codebase and introduces subtle bugs. It's somewhat depressing to fix all of them, knowing that nobody would use this anyway. With best regards, Tagir Valeev. [1] https://docs.oracle.com/javase/specs/jls/se14/preview/specs/records-jls.html#jls-8.10.1 [2] http://cr.openjdk.java.net/~gbierman/jep384/jep384-20200506/specs/records-jls.html#jls-8.10.1 From amaembo at gmail.com Fri Jul 24 04:06:14 2020 From: amaembo at gmail.com (Tagir Valeev) Date: Fri, 24 Jul 2020 11:06:14 +0700 Subject: [records] Is C-style array declaration in components dropped? In-Reply-To: References: Message-ID: Btw if this change is intended, then it looks like the changes in section 10.2 [1] must be dropped as well The array type of a variable depends on the bracket pairs that may appear as part of the type at the beginning of a variable declaration, or as part of the declarator for the variable, or both. Specifically, in the declaration of a field, formal parameter, local variable, or record component (8.3, 8.4.1, 9.3, 9.4, 14.4.1, 14.14.2, 15.27.1, 8.10.1), the array type of the variable is denoted by <...> Now, this text is inconsistent with 8.10.1. With best regards, Tagir Valeev. [1] http://cr.openjdk.java.net/~gbierman/jep384/jep384-20200506/specs/records-jls.html#jls-10.2 On Fri, Jul 24, 2020 at 10:55 AM Tagir Valeev wrote: > > Hello! > > The JLS 14 record preview spec allows C-style array declaration in > components (like record R(int x[])) [1]: > > RecordComponent: > { VariableModifier } UnannType VariableDeclaratorId > VariableArityRecordComponent > VariableDeclaratorId: > Identifier [ Dims ] > > However, it appears that JLS 15 draft spec doesn't allow it anymore [2] > > RecordComponent: > { Annotation } UnannType Identifier > VariableArityRecordComponent > > This change is not listed in the draft spec prolog in "The changes are > the same as those in the first preview of Records in Java SE 14, > except for the following", so I overlooked it when updated the records > support in IntelliJ IDEA. Is this intended change? If yes, then > probably the changes section should be updated to include it as well. > > I must say that I heavily support this change. Supporting C-style > array declaration in records adds complexity in many places of our > codebase and introduces subtle bugs. It's somewhat depressing to fix > all of them, knowing that nobody would use this anyway. > > With best regards, > Tagir Valeev. > > [1] https://docs.oracle.com/javase/specs/jls/se14/preview/specs/records-jls.html#jls-8.10.1 > [2] http://cr.openjdk.java.net/~gbierman/jep384/jep384-20200506/specs/records-jls.html#jls-8.10.1 From brian.goetz at oracle.com Fri Jul 24 12:43:41 2020 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 24 Jul 2020 08:43:41 -0400 Subject: [records] Is C-style array declaration in components dropped? In-Reply-To: References: Message-ID: <29901fcd-8dc5-55d6-466e-4f58903865eb@oracle.com> As we've added new places or ways to declare variables (e.g., var, record components), we've been trying to not propagate this syntax mistake.? So I agree that this is the right behavior; I think the change just got lost in the shuffle. On 7/23/2020 11:55 PM, Tagir Valeev wrote: > Hello! > > The JLS 14 record preview spec allows C-style array declaration in > components (like record R(int x[])) [1]: > > RecordComponent: > { VariableModifier } UnannType VariableDeclaratorId > VariableArityRecordComponent > VariableDeclaratorId: > Identifier [ Dims ] > > However, it appears that JLS 15 draft spec doesn't allow it anymore [2] > > RecordComponent: > { Annotation } UnannType Identifier > VariableArityRecordComponent > > This change is not listed in the draft spec prolog in "The changes are > the same as those in the first preview of Records in Java SE 14, > except for the following", so I overlooked it when updated the records > support in IntelliJ IDEA. Is this intended change? If yes, then > probably the changes section should be updated to include it as well. > > I must say that I heavily support this change. Supporting C-style > array declaration in records adds complexity in many places of our > codebase and introduces subtle bugs. It's somewhat depressing to fix > all of them, knowing that nobody would use this anyway. > > With best regards, > Tagir Valeev. > > [1] https://docs.oracle.com/javase/specs/jls/se14/preview/specs/records-jls.html#jls-8.10.1 > [2] http://cr.openjdk.java.net/~gbierman/jep384/jep384-20200506/specs/records-jls.html#jls-8.10.1 From gavin.bierman at oracle.com Mon Jul 27 10:23:24 2020 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Mon, 27 Jul 2020 11:23:24 +0100 Subject: [records] Is C-style array declaration in components dropped? In-Reply-To: References: Message-ID: <8A274A2E-C655-443A-8EE3-4CE5ACDEFC7E@oracle.com> Thanks. Yes, we did remove this syntax deliberately - apologies that it didn?t make the change list. I?ll tidy up the remaining inconsistency in the first version of the next records JEP JLS doc. Gavin > On 24 Jul 2020, at 05:06, Tagir Valeev wrote: > > Btw if this change is intended, then it looks like the changes in > section 10.2 [1] must be dropped as well > > The array type of a variable depends on the bracket pairs that may > appear as part of the type at the beginning of a variable declaration, > or as part of the declarator for the variable, or both. Specifically, > in the declaration of a field, formal parameter, local variable, or > record component (8.3, 8.4.1, 9.3, 9.4, 14.4.1, 14.14.2, 15.27.1, > 8.10.1), the array type of the variable is denoted by <...> > > Now, this text is inconsistent with 8.10.1. > > With best regards, > Tagir Valeev. > [1] http://cr.openjdk.java.net/~gbierman/jep384/jep384-20200506/specs/records-jls.html#jls-10.2 > > > > On Fri, Jul 24, 2020 at 10:55 AM Tagir Valeev wrote: >> >> Hello! >> >> The JLS 14 record preview spec allows C-style array declaration in >> components (like record R(int x[])) [1]: >> >> RecordComponent: >> { VariableModifier } UnannType VariableDeclaratorId >> VariableArityRecordComponent >> VariableDeclaratorId: >> Identifier [ Dims ] >> >> However, it appears that JLS 15 draft spec doesn't allow it anymore [2] >> >> RecordComponent: >> { Annotation } UnannType Identifier >> VariableArityRecordComponent >> >> This change is not listed in the draft spec prolog in "The changes are >> the same as those in the first preview of Records in Java SE 14, >> except for the following", so I overlooked it when updated the records >> support in IntelliJ IDEA. Is this intended change? If yes, then >> probably the changes section should be updated to include it as well. >> >> I must say that I heavily support this change. Supporting C-style >> array declaration in records adds complexity in many places of our >> codebase and introduces subtle bugs. It's somewhat depressing to fix >> all of them, knowing that nobody would use this anyway. >> >> With best regards, >> Tagir Valeev. >> >> [1] https://docs.oracle.com/javase/specs/jls/se14/preview/specs/records-jls.html#jls-8.10.1 >> [2] http://cr.openjdk.java.net/~gbierman/jep384/jep384-20200506/specs/records-jls.html#jls-8.10.1 From gavin.bierman at oracle.com Mon Jul 27 10:53:52 2020 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Mon, 27 Jul 2020 11:53:52 +0100 Subject: Finalizing in JDK 16 - Pattern matching for instanceof Message-ID: 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. From gavin.bierman at oracle.com Mon Jul 27 10:54:45 2020 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Mon, 27 Jul 2020 11:54:45 +0100 Subject: Finalizing in JDK 16 - Records Message-ID: <576588AE-CE85-46B3-92AD-C13F536F8F9F@oracle.com> [Second email of two, looking to close out features we hope to finalize in JDK 16.] Records ------- Record classes are a special kind of class that are used primarily to define a simple aggregate of values. Records can be thought of as _nominal tuples_; their declaration commits to a description of their state and given that their representation, as well as all of the interesting protocols an object might expose -- construction, property access, equality, etc -- are derived from that state description. Because we can derive everything from a common state description, the declaration can be extremely parsimonious. Here is an example of a record class declaration: record Point(int x, int y){} The state or, more formally, a record component list, (int x, int y), drives the implicit declaration of a number of members of the Point class. - A `private` field is declared for each record component - A `public` accessor method is declared for each record component - A constructor is declared with an argument list matching the record component list, and whose body assigns the fields with the corresponding argument. This constructor is called the _canonical constructor_. - Implementations of the methods: equals, toString and HashCode. The body of a record class declaration is often empty, but it can contain method declarations as usual. Indeed, if it is necessary, the implicitly declared members - the accessors, canonical constructor, and equals, toString, or HashCode methods -- can alternatively be explicitly declared in the body. Often the reason for explicitly providing a canonical constructor for a record class is to validate and/or normalize the argument values. To enhance the readability of record class declarations, we provide a new compact form of canonical constructor declaration, where only this validation/normalization code is required. Here is an example: record Rational(int num, int denom) { Rational { int gcd = gcd(num, denom); num /= gcd; denom /= gcd; } } The intention of a compact constructor declaration is that only validation and/or normalization code need be given in the constructor body; the remaining initialization code is automatically supplied by the compiler. The formal argument list is not required in a compact constructor declaration as it is taken from the record component list. In other words, this declaration is equivalent to the following one that uses the conventional constructor form: record Rational(int num, int denom) { Rational(int num, int demon) { // Validation/Normalization int gcd = gcd(num, denom); num /= gcd; denom /= gcd; // Initialization this.num = num; this.denom = denom; } } Once we settled on the design of record classes, things have been pretty stable. Three issues that did arise were: 1. Initially canonical constructors were required to be public. This was changed in the second preview. Now, if the canonical constructor is implicitly declared then its access modifier is the same as the record class. If it is explicitly declared then its access modifier must provide at least as much access as the record class. 2. We have extended the meaning of the `@Override` annotation to include the case that the annotated method is an explicitly declared accessor method for a record component. 3. To enforce the intended use of compact constructors, we made it a compile-time error to assign to any of the instance fields in the constructor body. One area that has generated a number of questions is annotations. Our intention is that an annotation on a record component is propagated to the field, accessor, and/or constructor parameter, according to the applicability of the annotation. It is not clear what other design choices there are. So we hope this is just something that has to be learnt, and afterwards it feels natural. The records JEP also allows for local record declarations. This is important as records will often be used as containers for intermediate data within method bodies. Being able to declare these record classes locally is essential to stop proliferation of classes. We are aware of some small tweaks that will be required to the specification during the second preview period, but overall this feature has not generated any controversy. From fredt at users.sourceforge.net Mon Jul 27 12:32:27 2020 From: fredt at users.sourceforge.net (Fred Toussi) Date: Mon, 27 Jul 2020 13:32:27 +0100 Subject: Finalizing in JDK 16 - Pattern matching for instanceof In-Reply-To: References: Message-ID: <1e56ddd2-4e1f-4abb-ad33-3dee7c5d3535@www.fastmail.com> 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. From brian.goetz at oracle.com Mon Jul 27 20:25:19 2020 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 27 Jul 2020 16:25:19 -0400 Subject: Fwd: Observation about nulls in type patterns In-Reply-To: <99e2350d-8c38-9d72-22ed-ce5d4cff0849@lidestrom.se> References: <99e2350d-8c38-9d72-22ed-ce5d4cff0849@lidestrom.se> Message-ID: Received on the -comments list. Indeed, we're well aware of the "action at a distance" concern you raise. Zooming out, we have three broad choices for addressing the null-hostility of switch as we generalize the switch feature to support patterns: ?- Abandon the existing switch construct for dead, and make a new one with similar but different semantics (sometimes called "snitch"); ?- Accept the current limitations of switch, and propagate them foreward, potentially creating new anomalies (such as the inability to refactor if-else chains to switch); ?- Partially rehabilitate the old feature, looking for ways to slot the new functionality in between the obstacles of the old constraints. All of these have pros and cons, and within each, there are sub-approaches with pros and cons.? We struggled with the same set of issues with expression switch; there, the main legacy constraint there was partiality (switch statements are partial, but expressions must be total.)? Again, we had the same basic three directions (with many sub-directions), and concluded that rehabilitating switch was better than either having both "switch" and "snitch", or allowing partial switch expressions which would throw at runtime.? The cost there is that you have to look more closely at whether a switch is used as a statement or expression to determine whether the cases cover all the possibilities. With patterns, and with nullity, we again prefer the rehabilitation path, and down that path, have explored several variants, such as separate pattern denotations for non-nullable and nullable (e.g., T vs T?), modifiers ("nullable-switch"), etc. The con of the path we have taken, as you note, is that (especially if you combine multiple features, like type inference and pattern switch), it is not as obvious whether a pattern is total or not. Of course, if you want to make it more obvious, you can write more explicit code.? You can, for example, refactor switch (o.get1().get2()) { ??? case Integer i: ... ??? case Number n: ... } into Number t = o.get1().get2(); switch (t) { ??? case Integer i: ... ??? case Number n: ... } and now it is more obvious again. One thing that leads me to consider this as the least-bad alternative is that switches throwing NPE today are not a serious problem, nor do developers universally have to wrap a null check around switches to make this so.? Which suggests that this particular edge is not the sharpest, so making it even less sharp (though more irregular) is unlikely to cause serious pain. And, it's not like there's a pain-free approach here; there's only tradeoffs that move it around.? Just like with everything else regarding null.? But, we hope the preview process will either validate, or invalidate, this hypothesis -- I think we've gotten as far as we can go with looking at it as a theoretical problem. Cheers, -Brian -------- Forwarded Message -------- Subject: Observation about nulls in type patterns Date: Sat, 25 Jul 2020 21:51:37 +0200 From: Jens Lidestr?m To: amber-spec-comments at openjdk.java.net Brian Goetz posted a write-up of the feature Types patterns in switch in amber-spec-experts on Jun 24: https://mail.openjdk.java.net/pipermail/amber-spec-experts/2020-June/002235.html I'd like to contribute an observation about the consequences of the proposed mechanism for handling null values in switches: If I understand the proposal correctly the behaviour of null values in a switch will depend on the type of the expression being switch upon. This is potentially non-local information, for example when the expression is a field access or a method call. To understand the behaviour of null values in a switch readers will have to examine the source of the expression being switch upon. Also, if the type of a method or a field is being narrowed or widened that change might silently affect the behaviour of existing switches in other parts of the code. Example: switch (o.get1().get2()) { ??? case Integer i: ... ??? case Number n: ... } If get2 is some method that is declared to return Number then the second pattern is total and will accept null; if get2 is declared to return Object pattern the IS total and WILL accept null. The expert group is probably aware of this fact already but I think it deserves to be noted explicitly. Best regards, Jens Lidestr?m Random Passer-by From guy.steele at oracle.com Mon Jul 27 20:38:38 2020 From: guy.steele at oracle.com (Guy Steele) Date: Mon, 27 Jul 2020 16:38:38 -0400 Subject: Observation about nulls in type patterns In-Reply-To: References: <99e2350d-8c38-9d72-22ed-ce5d4cff0849@lidestrom.se> Message-ID: <0CCA0A0D-4CDE-4DB2-B3A2-8F0B7379681C@oracle.com> > On Jul 27, 2020, at 4:25 PM, Brian Goetz wrote: > > ... > If get2 is some method that is declared to return Number then the second > pattern is total and will accept null; if get2 is declared to return > Object pattern the IS total and WILL accept null. In the text above quoted from Jens Lidestr?m, is some text missing in the third line? Returning to an earlier part of your message: > Of course, if you want to make it more obvious, you can write more explicit code. You can, for example, refactor > > switch (o.get1().get2()) { > case Integer i: ... > case Number n: ... > } > > into > > Number t = o.get1().get2(); > switch (t) { > case Integer i: ... > case Number n: ... > } > > and now it is more obvious again. Presumably that latter could also be written as switch ((Number)(o.get1().get2())) { case Integer i: ... case Number n: ? } and it might become a popular idiom always to provide that explicit cast in switch statements, and especially switch expressions, when there might be any doubt as to whether the switch has total coverage? ?Guy From brian.goetz at oracle.com Mon Jul 27 20:57:30 2020 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 27 Jul 2020 16:57:30 -0400 Subject: Observation about nulls in type patterns In-Reply-To: <0CCA0A0D-4CDE-4DB2-B3A2-8F0B7379681C@oracle.com> References: <99e2350d-8c38-9d72-22ed-ce5d4cff0849@lidestrom.se> <0CCA0A0D-4CDE-4DB2-B3A2-8F0B7379681C@oracle.com> Message-ID: <7f0cfbc5-2326-d938-44c6-e30f6bd66ab4@oracle.com> > In the text above quoted from Jens Lidestr?m, is some text missing in > the third line? I forwarded the complete mail, but perhaps his thoughts were incomplete.... > > Presumably that latter could also be written as > > switch ((Number)(o.get1().get2())) { > ? ? case Integer i: ... > ? ? case Number n: ? > } > > and it might become a popular idiom always to provide that explicit > cast in switch statements, and especially switch expressions, when > there might be any doubt as to whether the switch has total coverage? > It could, and this would work when your brain-analysis was correct, but would otherwise hide errors.? The result of get2() has a dynamic type D, and a static type S.? If we cast to Number, we're asserting that D <: Number; if this is not true, then we'll get a CCE at runtime.? For code that is control-flow dominated by the test, program analysis is free to conclude the test succeeded (otherwise we'd not be executing that code), so we can conclude that, if it ever runs, the last pattern is total and the compiler can elide the dynamic test. But this has the drawback that we might be wrong about what `get2()` returns, and then it would CCE; presumably we are using type patterns in switch to _avoid_ CCEs.? If `get2` returns Object, and might return something that is not a subtype of Number, the compiler won't balk (Object is cast-convertible to Number), but we'll get a runtime error. What we really want to do is assert that the _static_ return type of `get2()` is a subtype of Number.? And for that, the language provides the obvious way to do that already: ???? Number n = o.get1().get2() ???? switch (n) { ... } From forax at univ-mlv.fr Mon Jul 27 21:05:22 2020 From: forax at univ-mlv.fr (Remi Forax) Date: Mon, 27 Jul 2020 23:05:22 +0200 (CEST) Subject: Observation about nulls in type patterns In-Reply-To: <0CCA0A0D-4CDE-4DB2-B3A2-8F0B7379681C@oracle.com> References: <99e2350d-8c38-9d72-22ed-ce5d4cff0849@lidestrom.se> <0CCA0A0D-4CDE-4DB2-B3A2-8F0B7379681C@oracle.com> Message-ID: <1998753312.306204.1595883922086.JavaMail.zimbra@u-pem.fr> > De: "Guy Steele" > ?: "Brian Goetz" > Cc: "amber-spec-experts" > Envoy?: Lundi 27 Juillet 2020 22:38:38 > Objet: Re: Observation about nulls in type patterns >> On Jul 27, 2020, at 4:25 PM, Brian Goetz < [ mailto:brian.goetz at oracle.com | >> brian.goetz at oracle.com ] > wrote: >> ... >> If get2 is some method that is declared to return Number then the second >> pattern is total and will accept null; if get2 is declared to return >> Object pattern the IS total and WILL accept null. > In the text above quoted from Jens Lidestr?m, is some text missing in the third > line? > Returning to an earlier part of your message: >> Of course, if you want to make it more obvious, you can write more explicit >> code. You can, for example, refactor >> switch (o.get1().get2()) { >> case Integer i: ... >> case Number n: ... >> } >> into >> Number t = o.get1().get2(); >> switch (t) { >> case Integer i: ... >> case Number n: ... >> } >> and now it is more obvious again. > Presumably that latter could also be written as > switch ((Number)(o.get1().get2())) { > case Integer i: ... > case Number n: ? > } > and it might become a popular idiom always to provide that explicit cast in > switch statements, and especially switch expressions, when there might be any > doubt as to whether the switch has total coverage? Instead of having an idiom, we can use a restricted Java-ish syntax. For me it's a lot like when you want a serializable lambda and you end up with an ad hoc java-ish syntax (Foo & Serializable) s -> ... So i propose Number | null, switch (o.get1().get2()) { case Integer i -> ... case Number | null n -> ? } it's a restricted syntax too, it's either "type | null" and it's obviously explicit, it also makes the rule that say if a switch accept null or not more obvious because it's decouple from the notion of a total coverage, if a case accept null then the switch accept null. > ?Guy R?mi From guy.steele at oracle.com Mon Jul 27 21:05:54 2020 From: guy.steele at oracle.com (Guy Steele) Date: Mon, 27 Jul 2020 17:05:54 -0400 Subject: Observation about nulls in type patterns In-Reply-To: <7f0cfbc5-2326-d938-44c6-e30f6bd66ab4@oracle.com> References: <99e2350d-8c38-9d72-22ed-ce5d4cff0849@lidestrom.se> <0CCA0A0D-4CDE-4DB2-B3A2-8F0B7379681C@oracle.com> <7f0cfbc5-2326-d938-44c6-e30f6bd66ab4@oracle.com> Message-ID: > On Jul 27, 2020, at 4:57 PM, Brian Goetz wrote: > > > >> In the text above quoted from Jens Lidestr?m, is some text missing in the third line? > > I forwarded the complete mail, but perhaps his thoughts were incomplete.... > >> >> Presumably that latter could also be written as >> >> switch ((Number)(o.get1().get2())) { >> case Integer i: ... >> case Number n: ? >> } >> >> and it might become a popular idiom always to provide that explicit cast in switch statements, and especially switch expressions, when there might be any doubt as to whether the switch has total coverage? >> > > It could, and this would work when your brain-analysis was correct, but would otherwise hide errors. The result of get2() has a dynamic type D, and a static type S. If we cast to Number, we're asserting that D <: Number; if this is not true, then we'll get a CCE at runtime. For code that is control-flow dominated by the test, program analysis is free to conclude the test succeeded (otherwise we'd not be executing that code), so we can conclude that, if it ever runs, the last pattern is total and the compiler can elide the dynamic test. > > But this has the drawback that we might be wrong about what `get2()` returns, and then it would CCE; presumably we are using type patterns in switch to _avoid_ CCEs. If `get2` returns Object, and might return something that is not a subtype of Number, the compiler won't balk (Object is cast-convertible to Number), but we'll get a runtime error. > > What we really want to do is assert that the _static_ return type of `get2()` is a subtype of Number. And for that, the language provides the obvious way to do that already: > > Number n = o.get1().get2() > switch (n) { ? } Indeed; but it is unfortunate that it requires a separate statement (actually, a declaration), so is relatively unsuitable for use with switch expressions. Too bad there isn?t such a thing as a ?static cast?. (Oooh! Ooh! ?(static ) ? ! :-). No, don?t do that; it?s a terrible pun on the word ?static?. But if this turns out to be a big problem, it does suggest that one could have a form of switch in which the static type of the switch expression is declared. I don?t suggest pursuing it now, but it?s one idea to keep in our back pocket (where we can firmly sit on it unless and until needed). From brian.goetz at oracle.com Mon Jul 27 21:29:25 2020 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 27 Jul 2020 17:29:25 -0400 Subject: Observation about nulls in type patterns In-Reply-To: References: <99e2350d-8c38-9d72-22ed-ce5d4cff0849@lidestrom.se> <0CCA0A0D-4CDE-4DB2-B3A2-8F0B7379681C@oracle.com> <7f0cfbc5-2326-d938-44c6-e30f6bd66ab4@oracle.com> Message-ID: <74d82060-994f-e486-af45-91580b2faca8@oracle.com> > > Too bad there isn?t such a thing as a ?static cast?. (Oooh! ?Ooh! > ??(static ) ? ! ?:-). No, don?t do that; it?s a > terrible pun on the word ?static?. Alternately, what is too bad is that there _is_ such a thing as a "static cast", but it uses the same syntax, and has the same sort of contextual problem that Jens has complained about here! Suppose we have: ??? class Foo { ??????? void m(Foo f) { ??????????? Foo fs = (Foo) f;? // static cast! ??????? } ??? } The cast to `Foo` is purely a static cast.? (Same with casts to things like `Foo` or `T[]` or `T`.)? Essentially, if the cast type is not reifiable, but the two types are cast-convertible (perhaps with an unchecked warning), then its a static type, otherwise its a dynamic type.? This fact has cause plenty of trouble in Valhalla, since having such a cast magically become a dynamic one would surely cause problems. But, yes, if casts could be explicitly denoted as static or dynamic, this would do the trick. > But if this turns out to be a big problem, it does suggest that one > could have a form of switch in which the static type of the switch > expression is declared. ?I don?t suggest pursuing it now, but it?s one > idea to keep in our back pocket (where we can firmly sit on it unless > and until needed). ... and we said the same thing the last time we were at this crossroads, with respect to totality in statement switches. Indeed, when the switch rehabiliation is complete, we can go back and ask what additional optional type checking we want to perform. The mistake of declaring variables as `T t` rather than `t : T` (where you can elide the type ascription in some cases) haunts us ever day ... if we had gone this way, you could get what you want quite naturally with ??? switch (x : T) { ... } as a static type assertion. From amaembo at gmail.com Mon Jul 27 21:41:18 2020 From: amaembo at gmail.com (Tagir Valeev) Date: Tue, 28 Jul 2020 04:41:18 +0700 Subject: Observation about nulls in type patterns In-Reply-To: <74d82060-994f-e486-af45-91580b2faca8@oracle.com> References: <99e2350d-8c38-9d72-22ed-ce5d4cff0849@lidestrom.se> <0CCA0A0D-4CDE-4DB2-B3A2-8F0B7379681C@oracle.com> <7f0cfbc5-2326-d938-44c6-e30f6bd66ab4@oracle.com> <74d82060-994f-e486-af45-91580b2faca8@oracle.com> Message-ID: Note that static cast could be implemented as a library method! public class ensure { static T type(T t) { return t; } } switch(ensure.type(o.get1().get2())) { ... } This is exactly a compile-time check :-) With best regards, Tagir Valeev. On Tue, Jul 28, 2020 at 4:29 AM Brian Goetz wrote: > > > > Too bad there isn?t such a thing as a ?static cast?. (Oooh! Ooh! ?(static ) ? ! :-). No, don?t do that; it?s a terrible pun on the word ?static?. > > > Alternately, what is too bad is that there _is_ such a thing as a "static cast", but it uses the same syntax, and has the same sort of contextual problem that Jens has complained about here! > > Suppose we have: > > class Foo { > void m(Foo f) { > Foo fs = (Foo) f; // static cast! > } > } > > The cast to `Foo` is purely a static cast. (Same with casts to things like `Foo` or `T[]` or `T`.) Essentially, if the cast type is not reifiable, but the two types are cast-convertible (perhaps with an unchecked warning), then its a static type, otherwise its a dynamic type. This fact has cause plenty of trouble in Valhalla, since having such a cast magically become a dynamic one would surely cause problems. > > But, yes, if casts could be explicitly denoted as static or dynamic, this would do the trick. > > But if this turns out to be a big problem, it does suggest that one could have a form of switch in which the static type of the switch expression is declared. I don?t suggest pursuing it now, but it?s one idea to keep in our back pocket (where we can firmly sit on it unless and until needed). > > > ... and we said the same thing the last time we were at this crossroads, with respect to totality in statement switches. Indeed, when the switch rehabiliation is complete, we can go back and ask what additional optional type checking we want to perform. > > The mistake of declaring variables as `T t` rather than `t : T` (where you can elide the type ascription in some cases) haunts us ever day ... if we had gone this way, you could get what you want quite naturally with > > switch (x : T) { ... } > > as a static type assertion. From brian.goetz at oracle.com Mon Jul 27 21:45:42 2020 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 27 Jul 2020 17:45:42 -0400 Subject: Observation about nulls in type patterns In-Reply-To: <1998753312.306204.1595883922086.JavaMail.zimbra@u-pem.fr> References: <99e2350d-8c38-9d72-22ed-ce5d4cff0849@lidestrom.se> <0CCA0A0D-4CDE-4DB2-B3A2-8F0B7379681C@oracle.com> <1998753312.306204.1595883922086.JavaMail.zimbra@u-pem.fr> Message-ID: Now we're just going in circles, we already discussed this one.? It creates at least as much trouble as it solves. One problem is that while this seems like an obvious syntactic trick when the patterns are as simple as they are today, what happens when you get to deconstruction patterns? Another is that you're kind of pretending this is a special case of an OR pattern, in which case you'd write it: ??? case Number n | null n: ... but now we need binding merging (which we simplified away, to everyone's pleasure.)? If you want to do this as a type pattern, you could interpret it as ??? case (Number | Null) n: ... where `Null t` is a special type pattern that only matches null; this is consistent with the treatment of | in catch, which is also like a pattern.? But this all seems pretty ad-hoc, and doesn't really help where we need it most, which is nesting: ???? Box bs; ???? switch (bs) { ? ?? ??? case Box(var x): ... // or ???????? case Box(String s): ... ???? } If this doesn't match Box(null), this will be a much bigger source of mistakes than this current concern.? It is pretty clear that both of the above want to match "all boxes", and people will routinely forget to say "| null" (or, be annoyed they do have to) in these cases. If we already had a `T?` type, there'd be a natural denotation of both nullable and non-nullable type patterns, and this would be less annoying, but we don't. So there's a reason we didn't pick this one the first time around. (Stepping back, I would remind you that we are in the classic trap of, after having considered the problem in entirety, trying to fix the things we don't like locally, without re-doing the global analysis, just becomes someone noticed one of the things we don't like for the first time?) On 7/27/2020 5:05 PM, Remi Forax wrote: > > Instead of having an idiom, we can use a restricted Java-ish syntax. > For me it's a lot like when you want a serializable lambda and you end > up with an ad hoc java-ish syntax (Foo & Serializable) s -> ... > > So i propose Number | null, > ? switch (o.get1().get2()) { > ? ? case Integer i -> ... > ? ? case Number | null n -> ? > ? } > > it's a restricted syntax too, it's either "type | null" and it's > obviously explicit, it also makes the rule that say if a switch accept > null or not more obvious because it's decouple from the notion of a > total coverage, if a case accept null then the switch accept null. From forax at univ-mlv.fr Mon Jul 27 22:42:08 2020 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 28 Jul 2020 00:42:08 +0200 (CEST) Subject: Observation about nulls in type patterns In-Reply-To: References: <99e2350d-8c38-9d72-22ed-ce5d4cff0849@lidestrom.se> <0CCA0A0D-4CDE-4DB2-B3A2-8F0B7379681C@oracle.com> <1998753312.306204.1595883922086.JavaMail.zimbra@u-pem.fr> Message-ID: <2114730306.354269.1595889728099.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "Remi Forax" , "Guy Steele" > Cc: "amber-spec-experts" > Envoy?: Lundi 27 Juillet 2020 23:45:42 > Objet: Re: Observation about nulls in type patterns > Now we're just going in circles, we already discussed this one. It creates at > least as much trouble as it solves. as far as i know, we envisage it as an or pattern and not an ad hoc syntax. > One problem is that while this seems like an obvious syntactic trick when the > patterns are as simple as they are today, what happens when you get to > deconstruction patterns? it acts as a type, so i don't see the issue > Another is that you're kind of pretending this is a special case of an OR > pattern, no, I did not say that > If you want to do this as a type pattern, you could interpret it as > case (Number | Null) n: ... > where `Null t` is a special type pattern that only matches null; this is > consistent with the treatment of | in catch, which is also like a pattern. But > this all seems pretty ad-hoc, and doesn't really help where we need it most, > which is nesting: > Box bs; > switch (bs) { > case Box(var x): ... > // or > case Box(String s): ... > } the idea is to be explicit so case Box(String|null s) -> ... match null. The patterns above don't. > If this doesn't match Box(null), this will be a much bigger source of mistakes > than this current concern. It is pretty clear that both of the above want to > match "all boxes", and people will routinely forget to say "| null" (or, be > annoyed they do have to) in these cases. or they will not want to match null and be annoyed by the following NPE. When you have a design issue where half of the planet think one way and half of the planet think the other way, the best is to have a kind of "mother may i" rule, basically, you ask the user to explicitly refine its intent. > If we already had a `T?` type, there'd be a natural denotation of both nullable > and non-nullable type patterns, and this would be less annoying, but we don't. yes, String | null is the same notation as String? with the advantage being restricted to pattern matching and not being String? so allowing Java to add String? in a possible future (it's a kind of String? with no string attached :) ) > So there's a reason we didn't pick this one the first time around. I believe it worth reconsidering it. > (Stepping back, I would remind you that we are in the classic trap of, after > having considered the problem in entirety, trying to fix the things we don't > like locally, without re-doing the global analysis, just becomes someone > noticed one of the things we don't like for the first time?) I'm well aware of that trap but we already agree to have "case null" so we are considering null as a special local case. R?mi > On 7/27/2020 5:05 PM, Remi Forax wrote: >> Instead of having an idiom, we can use a restricted Java-ish syntax. >> For me it's a lot like when you want a serializable lambda and you end up with >> an ad hoc java-ish syntax (Foo & Serializable) s -> ... >> So i propose Number | null, >> switch (o.get1().get2()) { >> case Integer i -> ... >> case Number | null n -> ? >> } >> it's a restricted syntax too, it's either "type | null" and it's obviously >> explicit, it also makes the rule that say if a switch accept null or not more >> obvious because it's decouple from the notion of a total coverage, if a case >> accept null then the switch accept null. From brian.goetz at oracle.com Mon Jul 27 23:07:20 2020 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 27 Jul 2020 19:07:20 -0400 Subject: Observation about nulls in type patterns In-Reply-To: <2114730306.354269.1595889728099.JavaMail.zimbra@u-pem.fr> References: <99e2350d-8c38-9d72-22ed-ce5d4cff0849@lidestrom.se> <0CCA0A0D-4CDE-4DB2-B3A2-8F0B7379681C@oracle.com> <1998753312.306204.1595883922086.JavaMail.zimbra@u-pem.fr> <2114730306.354269.1595889728099.JavaMail.zimbra@u-pem.fr> Message-ID: <3dce7fe6-4793-0863-823d-33297118c5f3@oracle.com> > When you have a design issue where half of the planet think one way > and half of the planet think the other way, > the best is to have a kind of "mother may i" rule, basically, you ask > the user to explicitly refine its intent. But I don't think it is half and half.? If you show people ??? switch (o) { ??????? case Foo(var x, var y, var z): ... ??? } and ask them "will this match all Foos", 999 out of 1000 will say "of course, what else could it do?"?? THe idea that this might fail if z happened to be null would not occur to anyone. The semantics we've proposed stem from the above observation, plus the requirement that `var` mean type inference, and not something more subtle.? If you are saying `var`, you should be able to substitute it for the sensibly inferred type (if it is denotable) and get the same semantics.? This is not a random "gee, maybe this works." Where things are troubling are not the pattern semantics, but how it interacts with `switch`.? So, if you think the status quo is unacceptable, any "fix" needs to be closer to the switch interaction. From forax at univ-mlv.fr Mon Jul 27 23:43:31 2020 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 28 Jul 2020 01:43:31 +0200 (CEST) Subject: Observation about nulls in type patterns In-Reply-To: <3dce7fe6-4793-0863-823d-33297118c5f3@oracle.com> References: <99e2350d-8c38-9d72-22ed-ce5d4cff0849@lidestrom.se> <0CCA0A0D-4CDE-4DB2-B3A2-8F0B7379681C@oracle.com> <1998753312.306204.1595883922086.JavaMail.zimbra@u-pem.fr> <2114730306.354269.1595889728099.JavaMail.zimbra@u-pem.fr> <3dce7fe6-4793-0863-823d-33297118c5f3@oracle.com> Message-ID: <1951629945.357676.1595893411152.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "Guy Steele" , "amber-spec-experts" > Envoy?: Mardi 28 Juillet 2020 01:07:20 > Objet: Re: Observation about nulls in type patterns >> When you have a design issue where half of the planet think one way >> and half of the planet think the other way, >> the best is to have a kind of "mother may i" rule, basically, you ask >> the user to explicitly refine its intent. > > But I don't think it is half and half.? If you show people > > ??? switch (o) { > ??????? case Foo(var x, var y, var z): ... > ??? } > > and ask them "will this match all Foos", 999 out of 1000 will say "of > course, what else could it do?"?? The idea that this might fail if z > happened to be null would not occur to anyone. Null also means it doesn't exist for a lot of people, so obviously, it doesn't match. Anyway, that's not the problem, the problem here is that the semantics depend if the pattern is total or not and if you ask people does this pattern is total or not, you will not get a 1 per 1000 ratio of right answers. > > The semantics we've proposed stem from the above observation, plus the > requirement that `var` mean type inference, and not something more > subtle.? If you are saying `var`, you should be able to substitute it > for the sensibly inferred type (if it is denotable) and get the same > semantics.? This is not a random "gee, maybe this works." I don't disagree, the problem is more than it's easy as a human to think that a pattern is total while it's not (and vice-versa) and the proposed rule is also prone to action at distance issues. > > Where things are troubling are not the pattern semantics, but how it > interacts with `switch`.? So, if you think the status quo is > unacceptable, any "fix" needs to be closer to the switch interaction. I don't disagree with the analysis of the problem, as you said, the problem is not new. But a solution based on the fact that people will compute that a pattern is total or not is not pragmatic. At least the C# solution is pragmatic, the rule is not pretty because the meaning of var inside a switch is not the same as outside a switch but at least you have a syntactic marker of how a pattern will interact with null. As i said the other solution i see is to be more explicit, switch (o) { case Foo(var|null x, var|null y, var|null z) -> ... } but it supposes that people don't like to have nullable components. R?mi