From amalloy at google.com Fri Apr 1 00:31:15 2022 From: amalloy at google.com (Alan Malloy) Date: Thu, 31 Mar 2022 17:31:15 -0700 Subject: Pattern assignment In-Reply-To: <7551e40d-558b-a4a3-b493-623a421e2012@oracle.com> References: <7551e40d-558b-a4a3-b493-623a421e2012@oracle.com> Message-ID: I'm certainly on board with a pattern-matching context that doesn't require a vacuous conditional. Remainder, as it often does to me, seems like the most likely point of confusion, but if we believe Java developers can get their heads around the idea of remainder in other contexts, I don't think this one is a novel problem. I don't immediately see the benefit of partial patterns: why should I write let Optional.of(foo) = x; else foo = defaultFoo; when I could instead write (I assume blank finals are valid pattern variables?) final Foo foo; if (!(x instanceof Optional.of(foo))) foo = defaultFoo; Obviously it's shorter, but I'm not sure that's worth giving up the promised simplicity from earlier that `let` is for when "we know a pattern will always match". Let-expressions seem like a reasonable extension, though who knows how popular it will be. Of course, we could always generalize and add statement-expressions instead...alas, such a change will have to wait quite a while longer, I'm sure. Did you consider allowing pattern parameters only in lambdas, not in methods in general? Since a lambda is generally "internal implementation" and a method is often API-defining, it might be reasonable to allow implementation details to leak into lambda definitions if it makes them more convenient to write, while keeping the more formal separation of implementation and API for method parameters. On Fri, Mar 25, 2022 at 8:39 AM Brian Goetz wrote: > We still have a lot of work to do on the current round of pattern matching > (record patterns), but let's take a quick peek down the road. Pattern > assignment is a sensible next building block, not only because it is > directly useful, but also because it will be required for _declaring_ > deconstruction patterns in classes (that's how one pattern delegates to > another.) What follows is a rambling sketch of all the things we _could_ > do with pattern assignment, though we need not do all of them initially, or > even ever. > > > # Pattern assignment > > So far, we've got two contexts in the language that can accommodate > patterns -- > `instanceof` and `switch`. Both of these are conditional contexts, > designed for > dealing with partial patterns -- test whether a pattern matches, and if so, > conditionally extract some state and act on it. > > There are cases, though, when we know a pattern will always match, in > which case > we'd like to spare ourselves the ceremony of asking. If we have a 3d > `Point`, > asking if it is a `Point` is redundant and distracting: > > ``` > Point p = ... > if (p instanceof Point(var x, var y, var z)) { > // use x, y, z > } > ``` > > In this situation, we're asking a question to which we know the answer, and > we're distorting the structure of our code to do it. Further, we're > depriving > ourselves of the type checking the compiler would willingly do to validate > that > the pattern is total. Much better to have a way to _assert_ that the > pattern > matches. > > ## Let-bind statements > > In such a case, where we want to assert that the pattern matches, and > forcibly > bind it, we'd rather say so directly. We've experimented with a few ways > to > express this, and the best approach seems to be some sort of `let` > statement: > > ``` > let Point(var x, var y, var z) p = ...; > // can use x, y, z, p > ``` > > Other ways to surface this might be to call it `bind`: > > ``` > bind Point(var x, var y, var z) p = ...; > ``` > > or even use no keyword, and treat it as a generalization of assignment: > > ``` > Point(var x, var y, var z) p = ...; > ``` > > (Usual disclaimer: we discuss substance before syntax.) > > A `let` statement takes a pattern and an expression, and we statically > verify > that the pattern is exhaustive on the type of the expression; if it is > not, this is a > type error at compile time. Any bindings that appear in the pattern are > definitely assigned and in scope in the remainder of the block that > encloses the > `let` statement. > > Let statements are also useful in _declaring_ patterns; just as a subclass > constructor will delegate part of its job to a superclass constructor, a > subclass deconstruction pattern will likely want to delegate part of its > job to > a superclass deconstruction pattern. Let statements are a natural way to > invoke > total patterns from other total patterns. > > #### Remainder > > Let statements require that the pattern be exhaustive on the type of the > expression. > For total patterns like type patterns, this means that every value is > matched, > including `null`: > > ``` > let Object o = x; > ``` > > Whatever the value of `x`, `o` will be assigned to `x` (even if `x` is > null) > because `Object o` is total on `Object`. Similarly, some patterns are > clearly > not total on some types: > > ``` > Object o = ... > let String s = o; // compile error > ``` > > Here, `String s` is not total on `Object`, so the `let` statement is not > valid. > But as previously discussed, there is a middle ground -- patterns that are > _total with remainder_ -- which are "total enough" to be allowed to be > considered > exhaustive, but which in fact do not match on certain "weird" values. An > example is the record pattern `Box(var x)`; it matches all box instances, > even > those containing null, but does not match a `null` value itself (because to > deconstruct a `Box`, we effectively have to invoke an instance member on > the > box, and we cannot invoke instance members on null receivers.) Similarly, > the > pattern `Box(Bag(String s))` is total on `Box>`, with remainder > `null` and `Box(null)`. > > Because `let` statements guarantee that its bindings are definitely > assigned > after the `let` statement completes normally, the natural thing to do when > presented with a remainder value is to complete abruptly by reason of > exception. > (This is what `switch` does as well.) So the following statement: > > ``` > Box> bbs = ... > let Box(Bag(String s)) = bbs; > ``` > > would throw when encountering `null` or `Box(null)` (but not > `Box(Bag(null))`, > because that matches the pattern, with `s=null`, just like a switch > containing > only this case would. > > #### Conversions > > JLS Chapter 5 ("Conversions and Contexts") outlines the conversions > (widening, > narrowing, boxing, unboxing, etc) that are permitted in various contexts > (assignment, loose method invocation, strict method invocation, cast, > etc.) > We need to define the set of conversions we're willing to perform in the > context > of a `let` statement as well; which of the following do we want to support? > > ``` > let int x = aShort; // primitive widening > let byte b = 0; // primitive narrowing > let Integer x = 0; // boxing > let int x = anInteger; // unboxing > ``` > > The above examples -- all of which use type patterns -- look a lot like > local > variable declarations (especially if we choose to go without a keyword); > this > strongly suggests we should align the valid set of conversions in `let` > statements with those permitted in assignment context. The one place > where we > have to exercise care is conversions that involve unboxing; a null in such > circumstances feeds into the remainder of the pattern, rather than having > matching throw (we're still likely to throw, but it affects the timing of > how > far we progress in a pattern switch before we do so.) So for example, the > the pattern `int x` is exhaustive on `Integer`, but with remainder `null`. > > ## Possible extensions > > There are a number of ways we can extend `let` statements to make it more > useful; these could be added at the same time, or at a later time. > > #### What about partial patterns? > > There are times when it may be more convenient to use a `let` even when we > know > the pattern is partial. In most cases, we'll still want to complete > abruptly if the > pattern doesn't match, but we may want to control what happens. For > example: > > ``` > let Optional.of(var contents) = optName > else throw new IllegalArgumentException("name is empty"); > ``` > > Having an `else` clause allows us to use a partial pattern, which receives > control if the pattern does not match. The `else` clause could choose to > throw, > but could also choose to `break` or `return` to an enclosing context, or > even > recover by assigning the bindings. > > #### What about recovery? > > If we're supporting partial patterns, we might want to allow the `else` > clause > to provide defaults for the bindings, rather than throw. We can make the > bindings of the > pattern in the `let` statement be in scope, but definitely unassigned, in > the > `else` clause, which means the `else` clause could initialize them and > continue: > > ``` > let Optional.of(var contents) = optName > else contents = "Unnamed"; > ``` > > This allows us to continue, while preserving the invariant that when the > `let` > statement completes normally, all bindings are DA. > > #### What about guards > > If we're supporting partial patterns, we also need to consider the case > where > the pattern matches but we still want to reject the content. This could of > course be handled by testing and throwing after the `let` completes, but > if we > want to recover via the `else` clause, we might want to handle this > directly. > We've already introduced a means to do this for switch cases -- a `when` > clause > -- and this works equally well in `let`: > > ``` > let Point(var x, var y) = aPoint > when x >= 0 && y >= 0 > else { x = y = 0; } > ``` > > #### What about expressions? > > The name `let` conjures up the image of `let` expressions in functional > languages, where we introduce a local binding for use in the scope of a > single > expression. This is not an accident! It is quite useful when the same > expression > is going to be used multiple times, or when we want to limit the scope of > a local > to a specific computation. > > It is a short hop to `let` being usable as an expression, by providing an > `in` > clause: > > ``` > String lastThree = > let int len = s.length() > in s.substring(len-3, len); > ``` > > The scope of the binding `len` is the expression to the right of the `in`, > nothing else. (As with `switch` expressions, the expression to the right > of the `in` could be a block with a `yield` statement.) > > It is a further short hop to permitting _multiple_ matches in a single > `let` > statement or expression: > > ``` > int area = let Point(var x0, var y0) = lowerLeft, > Point(var x1, var y1) = upperRight > in (x1-x0) * (y1-y0); > ``` > > #### What about parameter bindings? > > Destructuring with total patterns is also useful for method and lambda > parameters. For a lambda that accepts a `Point`, we could include the > pattern > in the lambda parameter list, and the bindings would automatically be in > scope in the body. Instead of: > > ``` > areaFn = (Point lowerLeft, Point upperRight) > -> (upperRight.x() - lowerLeft.x()) * (upperRight.y() - > lowerLeft.y()); > ``` > > we could do the destructuring in the lambda header: > > ``` > areaFn = (let Point(var x0, var y0) lowerLeft, > let Point(var x1, var y1) upperRight) > -> (x1-x0) * (y1-y0); > ``` > > This allows us to treat the derived values to be "parameters" of the > lambda. We > would enforce totality at compile time, and dynamically reject remainder > as we > do with `switch` and `let` statements. > > I think this one may be a bridge too far, though. The method header should > probably be reserved for API declaration, and destructuring only serves > the > implementation. I think I'd prefer to move the `let` into the body of the > method or lambda. > > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From amalloy at google.com Fri Apr 1 00:54:08 2022 From: amalloy at google.com (Alan Malloy) Date: Thu, 31 Mar 2022 17:54:08 -0700 Subject: Remainder in pattern matching In-Reply-To: References: Message-ID: It seems pretty hard to land anywhere other than where you've landed, for most of this. I have the same sort of question as Dan: do we really want to wrap exceptions thrown by other patterns? You say we want to discourage patterns from throwing at all, and that's a lovely dream, but the behavior of total patterns is to throw when they meet something in their remainder. Since user-defined patterns will surely involve primitive patterns at some point, there is the possibility that one of those primitive patterns throws, which bubbles up as an exception thrown by a user-defined pattern. On Wed, Mar 30, 2022 at 7:40 AM Brian Goetz wrote: > We should have wrapped this up a while ago, so I apologize for the late > notice, but we really have to wrap up exceptions thrown from pattern > contexts (today, switch) when an exhaustive context encounters a > remainder. I think there's really one one sane choice, and the only thing > to discuss is the spelling, but let's go through it. > > In the beginning, nulls were special in switch. The first thing is to > evaluate the switch operand; if it is null, switch threw NPE. (I don't > think this was motivated by any overt null hostility, at least not at > first; it came from unboxing, where we said "if its a box, unbox it", and > the unboxing throws NPE, and the same treatment was later added to enums > (though that came out in the same version) and strings.) > > We have since refined switch so that some switches accept null. But for > those that don't, I see no other move besides "if the operand is null and > there is no null handling case, throw NPE." Null will always be a special > remainder value (when it appears in the remainder.) > > In Java 12, when we did switch expressions, we had to confront the issue > of novel enum constants. We considered a number of alternatives, and came > up with throwing ICCE. This was a reasonable choice, though as it turns > out is not one that scales as well as we had hoped it would at the time. > The choice here is based on "the view of classfiles at compile time and run > time has shifted in an incompatible way." ICCE is, as Kevin pointed out, a > reliable signal that your classpath is borked. > > We now have two precedents from which to extrapolate, but as it turns out, > neither is really very good for the general remainder case. > > Recall that we have a definition of _exhaustiveness_, which is, at some > level, deliberately not exhaustive. We know that there are edge cases for > which it is counterproductive to insist that the user explicitly cover, > often for two reasons: one is that its annoying to the user (writing cases > for things they believe should never happen), and the other that it > undermines type checking (the most common way to do this is a default > clause, which can sweep other errors under the rug.) > > If we have an exhaustive set of patterns on a type, the set of possible > values for that type that are not covered by some pattern in the set is > called the _remainder_. Computing the remainder exactly is hard, but > computing an upper bound on the remainder is pretty easy. I'll say "x may > be in the remainder of P* on T" to indicate that we're defining the upper > bound. > > - If P* contains a deconstruction pattern P(Q*), null may be in the > remainder of P*. > - If T is sealed, instances of a novel subtype of T may be in the > remainder of P*. > - If T is an enum, novel enum constants of T may be in the remainder of > P*. > - If R(X x, Y y) is a record, and x is in the remainder of Q* on X, then > `R(x, any)` may be in the remainder of { R(q) : q in Q*} on R. > > Examples: > > sealed interface X permits X1, X2 { } > record X1(String s) implements X { } > record X2(String s) implements X { } > > record R(X x1, X x2) { } > > switch (r) { > case R(X1(String s), any): > case R(X2(String s), X1(String s)): > case R(X2(String s), X2(String s)): > } > > This switch is exhaustive. Let N be a novel subtype of X. So the > remainder includes: > > null, R(N, _), R(_, N), R(null, _), R(X2, null) > > It might be tempting to argue (in fact, someone has) that we should try to > pick a "root cause" (null or novel) and throw that. But I think this is > both excessive and unworkable. > > Excessive: This means that the compiler would have to enumerate the > remainder set (its a set of patterns, so this is doable) and insert an > extra synthetic clause for each. This is a lot of code footprint and > complexity for a questionable benefit, and the sort of place where bugs > hide. > > Unworkable: Ultimately such code will have to make an arbitrary choice, > because R(N, null) and R(null, N) are in the remainder set. So which is > the root cause? Null or novel? We'd have to make an arbitrary choice. > > > So what I propose is the following simple answer instead: > > - If the switch target is null and no case handles null, throw NPE. (We > know statically whether any case handles null, so this is easy and similar > to what we do today.) > - If the switch is an exhaustive enum switch, and no case handles the > target, throw ICCE. (Again, we know statically whether the switch is over > an enum type.) > - In any other case of an exhaustive switch for which no case handles the > target, we throw a new exception type, java.lang.MatchException, with an > error message indicating remainder. > > The first two rules are basically dictated by compatibility. In > hindsight, we might have not chosen ICCE in 12, and gone with the general > (third) rule instead, but that's water under the bridge. > > We need to wrap this up in the next few days, so if you've concerns here, > please get them on the record ASAP. > > > As a separate but not-separate exception problem, we have to deal with at > least two additional sources of exceptions: > > - A dtor / record acessor may throw an arbitrary exception in the course > of evaluating whether a case matches. > > - User code in the switch may throw an arbitrary exception. > > For the latter, this has always been handled by having the switch > terminate abruptly with the same exception, and we should continue to do > this. > > For the former, we surely do not want to swallow this exception (such an > exception indicates a bug). The choices here are to treat this the same > way we do with user code, throwing it out of the switch, or to wrap with > MatchException. > > I prefer the latter -- wrapping with MatchException -- because the > exception is thrown from synthetic code between the user code and the > ultimate thrower, which means the pattern matching feature is mediating > access to the thrower. I think we should handle this as "if a pattern > invoked from pattern matching completes abruptly by throwing X, pattern > matching completes abruptly with MatchException", because the specific X is > not a detail we want the user to bind to. (We don't want them to bind to > anything, but if they do, we want them to bind to the logical action, not > the implementation details.) > > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From amalloy at google.com Fri Apr 1 01:09:38 2022 From: amalloy at google.com (Alan Malloy) Date: Thu, 31 Mar 2022 18:09:38 -0700 Subject: Pattern coverage In-Reply-To: References: Message-ID: I found T-RecBase confusing until I understood that we were talking about a set of patterns, and using set-builder notation, rather than a specific pattern. Perhaps section 1 could devote a sentence or two to what it means for a set of patterns to cover a type. On Thu, Mar 24, 2022 at 10:39 AM Brian Goetz wrote: > I've put a document at > > http://cr.openjdk.java.net/~briangoetz/eg-attachments/Coverage.pdf > > which outlines a formal model for pattern coverage, including record > patterns and the effects of sealing. This refines the work we did > earlier. The document may be a bit rough so please let me know if you spot > any errors. The approach here should be more amenable to specification > than the previous approach. > > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Apr 1 13:48:05 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 1 Apr 2022 09:48:05 -0400 Subject: [External] : Re: Pattern assignment In-Reply-To: References: <7551e40d-558b-a4a3-b493-623a421e2012@oracle.com> Message-ID: <4ff22616-e1bd-b44e-a865-29f4e23077ca@oracle.com> > I'm certainly on board with a pattern-matching context that doesn't > require a vacuous conditional. Remainder, as it often does to me, > seems like the most likely point of confusion, but if we believe Java > developers can get their heads around the idea of remainder in other > contexts, I don't think this one is a novel problem. Remainder is hard; the idea that our definition of "exhaustive" is intentionally defective is subtle, and will surely elicit "lol java" reactions among those disinclined to think very hard.? I wonder if a better term than "exhaustive" would help, one that doesn't promise so much. > I don't immediately see the benefit of partial patterns: why should I > write (I assume you mean you don't see the benefit of *let* with partial patterns, since if all patterns were total this would just be multiple return.) > let Optional.of(foo) = x; > else foo = defaultFoo; Because of scoping, and because you can't have a pattern just write to a local, even a blank final.? (This could of course be made to work, but I would really rather avoid going there if we at all can. (Yes Remi, I know you're in favor of going there.)) > when I could instead write (I assume blank finals are valid pattern > variables?) > > final Foo foo; > if (!(x instanceof Optional.of(foo))) foo = defaultFoo; Not currently, and I'd like to avoid it.? One reason is that this looks too much like a factory invocation; another is that, if we ever have constant patterns, then it won't be clear whether `foo` above is a variable into which to write the answer, or a constant that is being matched to the result of the binding.? Both of these are fighting (with method invocation) for the concise syntax, and I'm not sure I want any of them to win, but they can't all win, and I am not ready to pick that winner yet.? But, we will probably have to confront this? in some form when we get to dtor declaration. But yes, the main value of the `else` is so that bindings can be via a fallback path and be in scope for the rest of the method.? The rest of `else` and `when` is mostly along for the ride.? And its likely that we wouldn't do all these forms initially, but I wanted to sketch out the whole design space before doing anything. > Obviously it's shorter, but I'm not sure that's worth giving up the > promised simplicity from earlier that `let`?is for when "we know a > pattern will always match". OK, so you see this as being mostly "for unconditional patterns". > Let-expressions seem like a reasonable extension, though who knows how > popular it will be. Of course, we could always generalize and add > statement-expressions instead...alas, such a change will have to wait > quite a while longer, I'm sure. Let expressions would alleviate some but not all of the cases for which general statement-expressions would.? They are not quite as good for "f = new Foo(); f.setX(3); yield f;", but (IMO) better for pulling common subexpressions into variables whose scope is confined to the expression. > Did you consider allowing pattern parameters only in lambdas, not in > methods in general? Since a lambda is generally "internal > implementation" and a method is often API-defining, it might be > reasonable to allow implementation details to leak into lambda > definitions if it makes them more convenient to write, while keeping > the more formal separation of implementation and API for method > parameters. Yes, but I didn't come up with a syntax I liked enough for both lambdas and let.? Perhaps I'll try some more. > > On Fri, Mar 25, 2022 at 8:39 AM Brian Goetz > wrote: > > We still have a lot of work to do on the current round of pattern > matching (record patterns), but let's take a quick peek down the > road.? Pattern assignment is a sensible next building block, not > only because it is directly useful, but also because it will be > required for _declaring_ deconstruction patterns in classes > (that's how one pattern delegates to another.)? What follows is a > rambling sketch of all the things we _could_ do with pattern > assignment, though we need not do all of them initially, or even > ever. > > > # Pattern assignment > > So far, we've got two contexts in the language that can > accommodate patterns -- > `instanceof` and `switch`.? Both of these are conditional > contexts, designed for > dealing with partial patterns -- test whether a pattern matches, > and if so, > conditionally extract some state and act on it. > > There are cases, though, when we know a pattern will always match, > in which case > we'd like to spare ourselves the ceremony of asking.? If we have a > 3d `Point`, > asking if it is a `Point` is redundant and distracting: > > ``` > Point p = ... > if (p instanceof Point(var x, var y, var z)) { > ??? // use x, y, z > } > ``` > > In this situation, we're asking a question to which we know the > answer, and > we're distorting the structure of our code to do it. Further, > we're depriving > ourselves of the type checking the compiler would willingly do to > validate that > the pattern is total.? Much better to have a way to _assert_ that > the pattern > matches. > > ## Let-bind statements > > In such a case, where we want to assert that the pattern matches, > and forcibly > bind it, we'd rather say so directly.? We've experimented with a > few ways to > express this, and the best approach seems to be some sort of `let` > statement: > > ``` > let Point(var x, var y, var z) p = ...; > // can use x, y, z, p > ``` > > Other ways to surface this might be to call it `bind`: > > ``` > bind Point(var x, var y, var z) p = ...; > ``` > > or even use no keyword, and treat it as a generalization of > assignment: > > ``` > Point(var x, var y, var z) p = ...; > ``` > > (Usual disclaimer: we discuss substance before syntax.) > > A `let` statement takes a pattern and an expression, and we > statically verify > that the pattern is exhaustive on the type of the expression; if > it is not, this is a > type error at compile time.? Any bindings that appear in the > pattern are > definitely assigned and in scope in the remainder of the block > that encloses the > `let` statement. > > Let statements are also useful in _declaring_ patterns; just as a > subclass > constructor will delegate part of its job to a superclass > constructor, a > subclass deconstruction pattern will likely want to delegate part > of its job to > a superclass deconstruction pattern.? Let statements are a natural > way to invoke > total patterns from other total patterns. > > #### Remainder > > Let statements require that the pattern be exhaustive on the type > of the expression. > For total patterns like type patterns, this means that every value > is matched, > including `null`: > > ``` > let Object o = x; > ``` > > Whatever the value of `x`, `o` will be assigned to `x` (even if > `x` is null) > because `Object o` is total on `Object`.? Similarly, some patterns > are clearly > not total on some types: > > ``` > Object o = ... > let String s = o;? // compile error > ``` > > Here, `String s` is not total on `Object`, so the `let` statement > is not valid. > But as previously discussed, there is a middle ground -- patterns > that are > _total with remainder_ -- which are "total enough" to be allowed > to be considered > exhaustive, but which in fact do not match on certain "weird" > values. An > example is the record pattern `Box(var x)`; it matches all box > instances, even > those containing null, but does not match a `null` value itself > (because to > deconstruct a `Box`, we effectively have to invoke an instance > member on the > box, and we cannot invoke instance members on null receivers.)? > Similarly, the > pattern `Box(Bag(String s))` is total on `Box>`, with > remainder > `null` and `Box(null)`. > > Because `let` statements guarantee that its bindings are > definitely assigned > after the `let` statement completes normally, the natural thing to > do when > presented with a remainder value is to complete abruptly by reason > of exception. > (This is what `switch` does as well.)? So the following statement: > > ``` > Box> bbs = ... > let Box(Bag(String s)) = bbs; > ``` > > would throw when encountering `null` or `Box(null)` (but not > `Box(Bag(null))`, > because that matches the pattern, with `s=null`, just like a > switch containing > only this case would. > > #### Conversions > > JLS Chapter 5 ("Conversions and Contexts") outlines the > conversions (widening, > narrowing, boxing, unboxing, etc) that are permitted in various > contexts > (assignment, loose method invocation, strict method invocation, > cast, etc.) > We need to define the set of conversions we're willing to perform > in the context > of a `let` statement as well; which of the following do we want to > support? > > ``` > let int x = aShort;???? // primitive widening > let byte b = 0;???????? // primitive narrowing > let Integer x = 0;????? // boxing > let int x = anInteger;? // unboxing > ``` > > The above examples -- all of which use type patterns -- look a lot > like local > variable declarations (especially if we choose to go without a > keyword); this > strongly suggests we should align the valid set of conversions in > `let` > statements with those permitted in assignment context. The one > place where we > have to exercise care is conversions that involve unboxing; a null > in such > circumstances feeds into the remainder of the pattern, rather than > having > matching throw (we're still likely to throw, but it affects the > timing of how > far we progress in a pattern switch before we do so.) So for > example, the > the pattern `int x` is exhaustive on `Integer`, but with remainder > `null`. > > ## Possible extensions > > There are a number of ways we can extend `let` statements to make > it more > useful; these could be added at the same time, or at a later time. > > #### What about partial patterns? > > There are times when it may be more convenient to use a `let` even > when we know > the pattern is partial.? In most cases, we'll still want to > complete abruptly if the > pattern doesn't match, but we may want to control what happens.? > For example: > > ``` > let Optional.of(var contents) = optName > else throw new IllegalArgumentException("name is empty"); > ``` > > Having an `else` clause allows us to use a partial pattern, which > receives > control if the pattern does not match.? The `else` clause could > choose to throw, > but could also choose to `break` or `return` to an enclosing > context, or even > recover by assigning the bindings. > > #### What about recovery? > > If we're supporting partial patterns, we might want to allow the > `else` clause > to provide defaults for the bindings, rather than throw.? We can > make the bindings of the > pattern in the `let` statement be in scope, but definitely > unassigned, in the > `else` clause, which means the `else` clause could initialize them > and continue: > > ``` > let Optional.of(var contents) = optName > else contents = "Unnamed"; > ``` > > This allows us to continue, while preserving the invariant that > when the `let` > statement completes normally, all bindings are DA. > > #### What about guards > > If we're supporting partial patterns, we also need to consider the > case where > the pattern matches but we still want to reject the content.? This > could of > course be handled by testing and throwing after the `let` > completes, but if we > want to recover via the `else` clause, we might want to handle > this directly. > We've already introduced a means to do this for switch cases -- a > `when` clause > -- and this works equally well in `let`: > > ``` > let Point(var x, var y) = aPoint > when x >= 0 && y >= 0 > else { x = y = 0; } > ``` > > #### What about expressions? > > The name `let` conjures up the image of `let` expressions in > functional > languages, where we introduce a local binding for use in the scope > of a single > expression.? This is not an accident!? It is quite useful when the > same expression > is going to be used multiple times, or when we want to limit the > scope of a local > to a specific computation. > > It is a short hop to `let` being usable as an expression, by > providing an `in` > clause: > > ``` > String lastThree = > ??? let int len = s.length() > ??? in s.substring(len-3, len); > ``` > > The scope of the binding `len` is the expression to the right of > the `in`, > nothing else.? (As with `switch` expressions, the expression to > the right > of the `in` could be a block with a `yield` statement.) > > It is a further short hop to permitting _multiple_ matches in a > single `let` > statement or expression: > > ``` > int area = let Point(var x0, var y0) = lowerLeft, > ?????????????? Point(var x1, var y1) = upperRight > ?????????? in (x1-x0) * (y1-y0); > ``` > > #### What about parameter bindings? > > Destructuring with total patterns is also useful for method and lambda > parameters.? For a lambda that accepts a `Point`, we could include > the pattern > in the lambda parameter list, and the bindings would automatically > be in scope in the body.? Instead of: > > ``` > areaFn = (Point lowerLeft, Point upperRight) > ???????? -> (upperRight.x() - lowerLeft.x()) * (upperRight.y() - > lowerLeft.y()); > ``` > > we could do the destructuring in the lambda header: > > ``` > areaFn = (let Point(var x0, var y0) lowerLeft, > ????????? let Point(var x1, var y1) upperRight) > ???????? -> (x1-x0) * (y1-y0); > ``` > > This allows us to treat the derived values to be "parameters" of > the lambda.? We > would enforce totality at compile time, and dynamically reject > remainder as we > do with `switch` and `let` statements. > > I think this one may be a bridge too far, though.? The method > header should > probably be reserved for API declaration, and destructuring only > serves the > implementation.? I think I'd prefer to move the `let` into the > body of the > method or lambda. > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Apr 1 13:56:29 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 1 Apr 2022 09:56:29 -0400 Subject: [External] : Re: Remainder in pattern matching In-Reply-To: References: Message-ID: <54c16b85-2b48-c87c-306d-8eb9bce0968a@oracle.com> > It seems pretty hard to land anywhere other than where you've landed, > for most of this. I have the same sort of question as Dan: do we > really want to wrap exceptions thrown by other patterns? You say we > want to discourage patterns from throwing at all, and that's a lovely > dream, but the behavior of total patterns is to throw when they meet > something in their remainder. Not exactly.? The behavior of *switch* is to throw when they meet something in the remainder of *all their patterns*.? For example: ??? Box> bbs = new Box(null); ??? switch (bbs) { ??????? case Box(Box(String s)): ... ??????? case null, Box b: ... ??? } has no remainder and will not throw.? Box(null) doesn't match the first pattern, because when we unroll to what amounts to ??? if (x instanceof Box alpha && alpha != null && alpha.value() instanceof Box beta && beta != null) { ??????? s = beta.value(); ... ??? } ??? else if (x == null || x instanceof Box) { ... } we never dereference something we don't know to be non-null.? So Box(null) doesn't match the first case, but the second case gets a shot at it.? Only if no case matches does switch throw; *pattern matching* should never throw.? (Same story with let, except its like a switch with one putatively-exhaustive case.) > Since user-defined patterns will surely involve primitive patterns at > some point, there is the possibility that one of those primitive > patterns throws, which bubbles up as an exception thrown by a > user-defined pattern. Again, primitive patterns won't throw, they just won't match.? Under the rules I outlined last time, if I have: ??? Box b = new Box(null); ??? switch (b) { ??????? case Box(int x): ... ??????? ... ??? } when we try to match Box(int x) to Box(null), it will not NPE, it will just not match, and we'll go on to the next case.? If all cases don't match, then the switch will throw ME, which is a failure of *exhaustiveness*, not a failure in *pattern matching*. Does this change your first statement? > > On Wed, Mar 30, 2022 at 7:40 AM Brian Goetz > wrote: > > We should have wrapped this up a while ago, so I apologize for the > late notice, but we really have to wrap up exceptions thrown from > pattern contexts (today, switch) when an exhaustive context > encounters a remainder.? I think there's really one one sane > choice, and the only thing to discuss is the spelling, but let's > go through it. > > In the beginning, nulls were special in switch.? The first thing > is to evaluate the switch operand; if it is null, switch threw > NPE.? (I don't think this was motivated by any overt null > hostility, at least not at first; it came from unboxing, where we > said "if its a box, unbox it", and the unboxing throws NPE, and > the same treatment was later added to enums (though that came out > in the same version) and strings.) > > We have since refined switch so that some switches accept null.? > But for those that don't, I see no other move besides "if the > operand is null and there is no null handling case, throw NPE."? > Null will always be a special remainder value (when it appears in > the remainder.) > > In Java 12, when we did switch expressions, we had to confront the > issue of novel enum constants.? We considered a number of > alternatives, and came up with throwing ICCE.? This was a > reasonable choice, though as it turns out is not one that scales > as well as we had hoped it would at the time.? The choice here is > based on "the view of classfiles at compile time and run time has > shifted in an incompatible way."? ICCE is, as Kevin pointed out, a > reliable signal that your classpath is borked. > > We now have two precedents from which to extrapolate, but as it > turns out, neither is really very good for the general remainder > case. > > Recall that we have a definition of _exhaustiveness_, which is, at > some level, deliberately not exhaustive. We know that there are > edge cases for which it is counterproductive to insist that the > user explicitly cover, often for two reasons: one is that its > annoying to the user (writing cases for things they believe should > never happen), and the other that it undermines type checking (the > most common way to do this is a default clause, which can sweep > other errors under the rug.) > > If we have an exhaustive set of patterns on a type, the set of > possible values for that type that are not covered by some pattern > in the set is called the _remainder_.? Computing the remainder > exactly is hard, but computing an upper bound on the remainder is > pretty easy.? I'll say "x may be in the remainder of P* on T" to > indicate that we're defining the upper bound. > > ?- If P* contains a deconstruction pattern P(Q*), null may be in > the remainder of P*. > ?- If T is sealed, instances of a novel subtype of T may be in the > remainder of P*. > ?- If T is an enum, novel enum constants of T may be in the > remainder of P*. > ?- If R(X x, Y y) is a record, and x is in the remainder of Q* on > X, then `R(x, any)` may be in the remainder of { R(q) : q in Q*} on R. > > Examples: > > ??? sealed interface X permits X1, X2 { } > ??? record X1(String s) implements X { } > ??? record X2(String s) implements X { } > > ??? record R(X x1, X x2) { } > > ??? switch (r) { > ???????? case R(X1(String s), any): > ???????? case R(X2(String s), X1(String s)): > ???????? case R(X2(String s), X2(String s)): > ??? } > > This switch is exhaustive.? Let N be a novel subtype of X.? So the > remainder includes: > > ??? null, R(N, _), R(_, N), R(null, _), R(X2, null) > > It might be tempting to argue (in fact, someone has) that we > should try to pick a "root cause" (null or novel) and throw that.? > But I think this is both excessive and unworkable. > > Excessive: This means that the compiler would have to enumerate > the remainder set (its a set of patterns, so this is doable) and > insert an extra synthetic clause for each.? This is a lot of code > footprint and complexity for a questionable benefit, and the sort > of place where bugs hide. > > Unworkable: Ultimately such code will have to make an arbitrary > choice, because R(N, null) and R(null, N) are in the remainder > set.? So which is the root cause?? Null or novel?? We'd have to > make an arbitrary choice. > > > So what I propose is the following simple answer instead: > > ?- If the switch target is null and no case handles null, throw > NPE.? (We know statically whether any case handles null, so this > is easy and similar to what we do today.) > ?- If the switch is an exhaustive enum switch, and no case handles > the target, throw ICCE.? (Again, we know statically whether the > switch is over an enum type.) > ?- In any other case of an exhaustive switch for which no case > handles the target, we throw a new exception type, > java.lang.MatchException, with an error message indicating remainder. > > The first two rules are basically dictated by compatibility.? In > hindsight, we might have not chosen ICCE in 12, and gone with the > general (third) rule instead, but that's water under the bridge. > > We need to wrap this up in the next few days, so if you've > concerns here, please get them on the record ASAP. > > > As a separate but not-separate exception problem, we have to deal > with at least two additional sources of exceptions: > > ?- A dtor / record acessor may throw an arbitrary exception in the > course of evaluating whether a case matches. > > ?- User code in the switch may throw an arbitrary exception. > > For the latter, this has always been handled by having the switch > terminate abruptly with the same exception, and we should continue > to do this. > > For the former, we surely do not want to swallow this exception > (such an exception indicates a bug).? The choices here are to > treat this the same way we do with user code, throwing it out of > the switch, or to wrap with MatchException. > > I prefer the latter -- wrapping with MatchException -- because the > exception is thrown from synthetic code between the user code and > the ultimate thrower, which means the pattern matching feature is > mediating access to the thrower.? I think we should handle this as > "if a pattern invoked from pattern matching completes abruptly by > throwing X, pattern matching completes abruptly with > MatchException", because the specific X is not a detail we want > the user to bind to.? (We don't want them to bind to anything, but > if they do, we want them to bind to the logical action, not the > implementation details.) > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From gavin.bierman at oracle.com Thu Apr 7 11:40:16 2022 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Thu, 7 Apr 2022 11:40:16 +0000 Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available Message-ID: Dear experts: The first draft of a spec covering both the third preview of Pattern Matching for switch (JEP number coming, but currently available at https://openjdk.java.net/jeps/8282272) and JEP 405 (Record Patterns) is now available at: http://cr.openjdk.java.net/~gbierman/PatternSwitchPlusRecordPatterns/PatternSwitchPlusRecordPatterns-20220407/specs/patterns-switch-jls.html Comments welcome! Thanks, Gavin -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Apr 7 19:41:02 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 7 Apr 2022 15:41:02 -0400 Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available In-Reply-To: References: Message-ID: <8eacf3a7-951f-e2a3-8ea2-de9230a9b514@oracle.com> > http://cr.openjdk.java.net/~gbierman/PatternSwitchPlusRecordPatterns/PatternSwitchPlusRecordPatterns-20220407/specs/patterns-switch-jls.html > > > Comments welcome! The execution of an exhaustive|switch|can fail with a linkage error (an|IncompatibleClassChangeError|is thrown) if it encounters an instance of a permitted direct subclass that was not known at compile time (14.11.3 ,15.28.2 ). Strictly speaking, the linkage error is not flagging a binary incompatible change of the|sealed|class, but more accurately a/migration incompatible/change of the|sealed|class. I think we should back away from ICCE here as well, and put this in the MatchException bucket too.? Then: ?- a switch throws NPE if the operand is null; ?- an _enum switch_ throws ICCE when encountering a novel constant; ?- all other remainder errors are MatchException. File away for future use, that these clauses will have to be extended to include other exhaustive pattern-aware constructs, like let. 14.11.1 Switch Blocks The grammar for CaseOrDefaultLabel seems like it could be profitably refactored to reflect more of the restrictions: ??? CaseOrDefaultLabel ??????? case (null | CaseConstant) {, CaseConstant } ??????? case [null, ] Pattern { WhenClause } ??????? case [null, ] default ??????? default and then you don't have to enumerate as many of the restrictions of what can combine with what. It is a compile-time error if a|when|expression has the value|false|. ... is a constant expression and has the value false ? * A pattern case element/p/is switch compatible with/T/if/p/is assignable to type/T/(14.30.3 ). Isn't this cast-convertible?? If the selector is String and the pattern is `Object o`, o is not assignable to String, but it is cast-convertible. A switch label is said to/dominate/another switch label Can we say that in a pattern switch, default dominates everything, which has the effect of forcing the default to the bottom? if there are values for which both apply and there is not an obvious preference Is this really what we mean?? Don't we really mean that the first one matches everything the second one does? A set of case elements is exhaustive This is a nit, but couldn't this be its own subsection?? This section is getting long and varied. /T/if/T/is downcast convertible to/U/ Is this right?? Upcast convertibility is OK too -- you can match `Object o` to a target of `String`, and vice versa. If the type/R/is a raw type (4.8 ) then the type/T/must be a raw type, or vice versa; otherwise a compile-time error occurs. Is this the right restriction?? What we want here (for this iteration) is that if R is generic, we specify the type parameters. But this is not the same thing.? I would think we would want to say here something like "if the class of R is a generic class, R cannot be raw". whose type names/R/ missing a word 1. A switch label that supports a pattern/p//applies/if the value matches/p/(14.30.2 ). If pattern matching completes abruptly then determining which switch label applies completes abruptly for the same reason. I think this is carried over from the previous round?? Or do we not resolve total type patterns to any at the top level of a switch? 2. If no|case|label matches but there is a|default|label, then the|default|label/matches/.*If neither of these rules apply to any of the switch labels in the switch block, then a switch label that supports a|default|applies.* Don't we need a clause that says "if there is no default, a MatchException is thrown"? *If pattern matching completes abruptly then the process of determining which switch label applies completes abruptly for the same reason.* Doesn't it complete abruptly with MatchException?? Or can PM only complete abruptly with ME as well? A type pattern that does not appear as an element in a record component pattern list is called a/top-level type pattern/. For future: "or array component pattern list" The pattern variable declared by an any pattern has a type, which is a reference type. Is this still true?? What if I have `record R(int x) {}` and `case R(var x)`?? The type of x is not a reference type.? Same for `case R(int x)`. A pattern/p/is said to be/unconditional/at a type/T/if every value of type/T/will match/p/(after/p/has been resolved at type/T/(14.30.2 )), and is defined as follows: An any pattern is unconditional at all T? -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Apr 7 19:54:11 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 7 Apr 2022 15:54:11 -0400 Subject: Primitive type patterns In-Reply-To: References: Message-ID: <89faf208-d461-fcab-aac7-af260351db89@oracle.com> There's another, probably stronger, reason why primitive patterns supporting widening, narrowing, boxing, unboxing, etc, are basically a forced move, besides alignment with `let` statements, discussed earlier: > There is another constraint on primitive type patterns: the let/bind > statement coming down the road.? Because a type pattern looks (not > accidentally) like a local variable declaration, a let/bind we will > want to align the semantics of "local variable declaration with > initializer" and "let/bind with total type pattern". Concretely: > > ??? let String s = "foo"; > > is a pattern match against the (total) pattern `String s`, which > introduces `s` into the remainder of the block.? Since let/bind is a > generalization of local variable declaration with initialization, > let/bind should align with locals where the two can express the same > thing.? This means that the set of conversions allowed in assignment > context (JLS 5.2) should also be supported by type patterns. The other reason is the duality with constructors (and eventually, methods).? if we have a record: ???? record R(int x, Long l) { } we can construct an R with ??? new R(int, Long)?????????????? // no adaptation ??? new R(Integer, Long)?????? // unbox x ??? new R(short, Long)????????? // widen x ??? new R(int, long)?????????????? // box y Deconstruction patterns are the dual of constructors; we should be able to deconstruct an R with: ??? case R(int x, Long l)??????????????? // no adaptation ??? case R(Integer x, Long l)??????? // box x ??? case R(short s, Long l)?????????? // range check ??? case R(int x, long l)??????????????? // unbox y, null check So the set of adaptations in method invocation context should align with those in nested patterns, too. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Thu Apr 7 21:10:52 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 7 Apr 2022 23:10:52 +0200 (CEST) Subject: Primitive type patterns In-Reply-To: <89faf208-d461-fcab-aac7-af260351db89@oracle.com> References: <89faf208-d461-fcab-aac7-af260351db89@oracle.com> Message-ID: <1159934987.8907800.1649365852163.JavaMail.zimbra@u-pem.fr> > From: "Brian Goetz" > To: "amber-spec-experts" > Sent: Thursday, April 7, 2022 9:54:11 PM > Subject: Re: Primitive type patterns > There's another, probably stronger, reason why primitive patterns supporting > widening, narrowing, boxing, unboxing, etc, are basically a forced move, > besides alignment with `let` statements, discussed earlier: >> There is another constraint on primitive type patterns: the let/bind statement >> coming down the road. Because a type pattern looks (not accidentally) like a >> local variable declaration, a let/bind we will want to align the semantics of >> "local variable declaration with initializer" and "let/bind with total type >> pattern". Concretely: >> let String s = "foo"; >> is a pattern match against the (total) pattern `String s`, which introduces `s` >> into the remainder of the block. Since let/bind is a generalization of local >> variable declaration with initialization, let/bind should align with locals >> where the two can express the same thing. This means that the set of >> conversions allowed in assignment context (JLS 5.2) should also be supported by >> type patterns. > The other reason is the duality with constructors (and eventually, methods). if > we have a record: > record R(int x, Long l) { } > we can construct an R with > new R(int, Long) // no adaptation > new R(Integer, Long) // unbox x > new R(short, Long) // widen x > new R(int, long) // box y > Deconstruction patterns are the dual of constructors; we should be able to > deconstruct an R with: > case R(int x, Long l) // no adaptation > case R(Integer x, Long l) // box x > case R(short s, Long l) // range check > case R(int x, long l) // unbox y, null check > So the set of adaptations in method invocation context should align with those > in nested patterns, too. We already discussed those rules when we discuss instanceof, it means that "x instanceof primitive" has different meaning depending on the type of x Object x = ... if (x instanceof short) { ... } // <=> instanceof Short + unboxing int x = ... if (x instanceof short) { ... } // <=> range check Byte x = ... if (x instanceof short) { ... } // <=> nullcheck + unboxing + widening You are overloading instanceof with different meanings, losing everybody apart the compiler in the process. It's also another creative way to have an action at distance, var x = ... f (x instanceof short) { ... } // <=> ??? We do not need all theses conversions to be part of the pattern, those conversions are already done as part expression/instruction after the ':' or '->' of the switch in an organic way. // with op(int, Long) case R(int x, Long l) -> op(x, l); // no adaptation // with op(Integer, Long) case R(int x, Long l) -> op(x, l); // box x // with op(int, long) case R(int x, Long l) -> op(x, l); // unbox l, nullcheck And for the range check, as i said earlier, it's better to use a pattern method with a good name so everybody will be able to read the code. R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Apr 7 21:24:05 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 7 Apr 2022 17:24:05 -0400 Subject: [External] : Re: Primitive type patterns In-Reply-To: <1159934987.8907800.1649365852163.JavaMail.zimbra@u-pem.fr> References: <89faf208-d461-fcab-aac7-af260351db89@oracle.com> <1159934987.8907800.1649365852163.JavaMail.zimbra@u-pem.fr> Message-ID: > We already discussed those rules when we discuss instanceof,? it means > that "x instanceof primitive" has different meaning depending on the > type of x No, it does not.? It means "does x match the pattern P" everywhere. It is pattern P is that has different meanings depending on type. This may sound like a silly distinction, but it is not!? Pattern matching is inherently polymorphic -- it is all about reflecting dynamic conversions statically -- and exhibits the *same* polymorphism regardless of where it occurs. > ? Object x = ... > ? if (x instanceof short) { ... }?? // <=> instanceof Short + unboxing Where x is a reference type, yes.? How is that not a reasonable question to ask??? And, why would we not want duality with: ??? record R(short s) { } ??? ... ??? new R(x) Asking `r instanceof R(short s)` is akin to asking "could this R have come from passing some x to the R constructor".?? And that would work if x were byte, or x were a long literal in the value range of short, or a Short, or.... > It's also another creative way to have an action at distance, More like *the same* way. Anyway, your opposition to the entirety of this sub-feature is noted. -------------- next part -------------- An HTML attachment was scrubbed... URL: From amalloy at google.com Thu Apr 7 21:38:23 2022 From: amalloy at google.com (Alan Malloy) Date: Thu, 7 Apr 2022 14:38:23 -0700 Subject: [External] : Re: Pattern assignment In-Reply-To: <4ff22616-e1bd-b44e-a865-29f4e23077ca@oracle.com> References: <7551e40d-558b-a4a3-b493-623a421e2012@oracle.com> <4ff22616-e1bd-b44e-a865-29f4e23077ca@oracle.com> Message-ID: On Fri, Apr 1, 2022 at 6:48 AM Brian Goetz wrote: > > I'm certainly on board with a pattern-matching context that doesn't > require a vacuous conditional. Remainder, as it often does to me, seems > like the most likely point of confusion, but if we believe Java developers > can get their heads around the idea of remainder in other contexts, I don't > think this one is a novel problem. > > > Remainder is hard; the idea that our definition of "exhaustive" is > intentionally defective is subtle, and will surely elicit "lol java" > reactions among those disinclined to think very hard. I wonder if a better > term than "exhaustive" would help, one that doesn't promise so much. > > I don't immediately see the benefit of partial patterns: why should I write > > > (I assume you mean you don't see the benefit of *let* with partial > patterns, since if all patterns were total this would just be multiple > return.) > > let Optional.of(foo) = x; > else foo = defaultFoo; > > > Because of scoping, and because you can't have a pattern just write to a > local, even a blank final. (This could of course be made to work, but I > would really rather avoid going there if we at all can. (Yes Remi, I know > you're in favor of going there.)) > > when I could instead write (I assume blank finals are valid pattern > variables?) > > final Foo foo; > if (!(x instanceof Optional.of(foo))) foo = defaultFoo; > > > Not currently, and I'd like to avoid it. One reason is that this looks > too much like a factory invocation; another is that, if we ever have > constant patterns, then it won't be clear whether `foo` above is a variable > into which to write the answer, or a constant that is being matched to the > result of the binding. Both of these are fighting (with method invocation) > for the concise syntax, and I'm not sure I want any of them to win, but > they can't all win, and I am not ready to pick that winner yet. But, we > will probably have to confront this in some form when we get to dtor > declaration. > Yes, I suppose it's uncomfortably close to a method call. My thinking was that, with foo a blank final (and therefore definitely unassigned), only a pattern match makes sense. Since patterns already rely on DA/UA analysis for scoping, this doesn't seem like that much of a further stretch. But, okay, it's not very pretty either, and I don't mind if we don't go this way. > > > But yes, the main value of the `else` is so that bindings can be via a > fallback path and be in scope for the rest of the method. The rest of > `else` and `when` is mostly along for the ride. And its likely that we > wouldn't do all these forms initially, but I wanted to sketch out the whole > design space before doing anything. > > Obviously it's shorter, but I'm not sure that's worth giving up the > promised simplicity from earlier that `let` is for when "we know a > pattern will always match". > > > OK, so you see this as being mostly "for unconditional patterns". > Yes. If I want to assign to a variable based on a pattern that might fail, why wouldn't I just use a switch expression? > Let-expressions seem like a reasonable extension, though who knows how > popular it will be. Of course, we could always generalize and add > statement-expressions instead...alas, such a change will have to wait quite > a while longer, I'm sure. > > > Let expressions would alleviate some but not all of the cases for which > general statement-expressions would. They are not quite as good for "f = > new Foo(); f.setX(3); yield f;", but (IMO) better for pulling common > subexpressions into variables whose scope is confined to the expression. > > Did you consider allowing pattern parameters only in lambdas, not in > methods in general? Since a lambda is generally "internal implementation" > and a method is often API-defining, it might be reasonable to allow > implementation details to leak into lambda definitions if it makes them > more convenient to write, while keeping the more formal separation of > implementation and API for method parameters. > > > Yes, but I didn't come up with a syntax I liked enough for both lambdas > and let. Perhaps I'll try some more. > > > On Fri, Mar 25, 2022 at 8:39 AM Brian Goetz > wrote: > >> We still have a lot of work to do on the current round of pattern >> matching (record patterns), but let's take a quick peek down the road. >> Pattern assignment is a sensible next building block, not only because it >> is directly useful, but also because it will be required for _declaring_ >> deconstruction patterns in classes (that's how one pattern delegates to >> another.) What follows is a rambling sketch of all the things we _could_ >> do with pattern assignment, though we need not do all of them initially, or >> even ever. >> >> >> # Pattern assignment >> >> So far, we've got two contexts in the language that can accommodate >> patterns -- >> `instanceof` and `switch`. Both of these are conditional contexts, >> designed for >> dealing with partial patterns -- test whether a pattern matches, and if >> so, >> conditionally extract some state and act on it. >> >> There are cases, though, when we know a pattern will always match, in >> which case >> we'd like to spare ourselves the ceremony of asking. If we have a 3d >> `Point`, >> asking if it is a `Point` is redundant and distracting: >> >> ``` >> Point p = ... >> if (p instanceof Point(var x, var y, var z)) { >> // use x, y, z >> } >> ``` >> >> In this situation, we're asking a question to which we know the answer, >> and >> we're distorting the structure of our code to do it. Further, we're >> depriving >> ourselves of the type checking the compiler would willingly do to >> validate that >> the pattern is total. Much better to have a way to _assert_ that the >> pattern >> matches. >> >> ## Let-bind statements >> >> In such a case, where we want to assert that the pattern matches, and >> forcibly >> bind it, we'd rather say so directly. We've experimented with a few ways >> to >> express this, and the best approach seems to be some sort of `let` >> statement: >> >> ``` >> let Point(var x, var y, var z) p = ...; >> // can use x, y, z, p >> ``` >> >> Other ways to surface this might be to call it `bind`: >> >> ``` >> bind Point(var x, var y, var z) p = ...; >> ``` >> >> or even use no keyword, and treat it as a generalization of assignment: >> >> ``` >> Point(var x, var y, var z) p = ...; >> ``` >> >> (Usual disclaimer: we discuss substance before syntax.) >> >> A `let` statement takes a pattern and an expression, and we statically >> verify >> that the pattern is exhaustive on the type of the expression; if it is >> not, this is a >> type error at compile time. Any bindings that appear in the pattern are >> definitely assigned and in scope in the remainder of the block that >> encloses the >> `let` statement. >> >> Let statements are also useful in _declaring_ patterns; just as a subclass >> constructor will delegate part of its job to a superclass constructor, a >> subclass deconstruction pattern will likely want to delegate part of its >> job to >> a superclass deconstruction pattern. Let statements are a natural way to >> invoke >> total patterns from other total patterns. >> >> #### Remainder >> >> Let statements require that the pattern be exhaustive on the type of the >> expression. >> For total patterns like type patterns, this means that every value is >> matched, >> including `null`: >> >> ``` >> let Object o = x; >> ``` >> >> Whatever the value of `x`, `o` will be assigned to `x` (even if `x` is >> null) >> because `Object o` is total on `Object`. Similarly, some patterns are >> clearly >> not total on some types: >> >> ``` >> Object o = ... >> let String s = o; // compile error >> ``` >> >> Here, `String s` is not total on `Object`, so the `let` statement is not >> valid. >> But as previously discussed, there is a middle ground -- patterns that are >> _total with remainder_ -- which are "total enough" to be allowed to be >> considered >> exhaustive, but which in fact do not match on certain "weird" values. An >> example is the record pattern `Box(var x)`; it matches all box instances, >> even >> those containing null, but does not match a `null` value itself (because >> to >> deconstruct a `Box`, we effectively have to invoke an instance member on >> the >> box, and we cannot invoke instance members on null receivers.) >> Similarly, the >> pattern `Box(Bag(String s))` is total on `Box>`, with >> remainder >> `null` and `Box(null)`. >> >> Because `let` statements guarantee that its bindings are definitely >> assigned >> after the `let` statement completes normally, the natural thing to do when >> presented with a remainder value is to complete abruptly by reason of >> exception. >> (This is what `switch` does as well.) So the following statement: >> >> ``` >> Box> bbs = ... >> let Box(Bag(String s)) = bbs; >> ``` >> >> would throw when encountering `null` or `Box(null)` (but not >> `Box(Bag(null))`, >> because that matches the pattern, with `s=null`, just like a switch >> containing >> only this case would. >> >> #### Conversions >> >> JLS Chapter 5 ("Conversions and Contexts") outlines the conversions >> (widening, >> narrowing, boxing, unboxing, etc) that are permitted in various contexts >> (assignment, loose method invocation, strict method invocation, cast, >> etc.) >> We need to define the set of conversions we're willing to perform in the >> context >> of a `let` statement as well; which of the following do we want to >> support? >> >> ``` >> let int x = aShort; // primitive widening >> let byte b = 0; // primitive narrowing >> let Integer x = 0; // boxing >> let int x = anInteger; // unboxing >> ``` >> >> The above examples -- all of which use type patterns -- look a lot like >> local >> variable declarations (especially if we choose to go without a keyword); >> this >> strongly suggests we should align the valid set of conversions in `let` >> statements with those permitted in assignment context. The one place >> where we >> have to exercise care is conversions that involve unboxing; a null in such >> circumstances feeds into the remainder of the pattern, rather than having >> matching throw (we're still likely to throw, but it affects the timing of >> how >> far we progress in a pattern switch before we do so.) So for example, >> the >> the pattern `int x` is exhaustive on `Integer`, but with remainder `null`. >> >> ## Possible extensions >> >> There are a number of ways we can extend `let` statements to make it more >> useful; these could be added at the same time, or at a later time. >> >> #### What about partial patterns? >> >> There are times when it may be more convenient to use a `let` even when >> we know >> the pattern is partial. In most cases, we'll still want to complete >> abruptly if the >> pattern doesn't match, but we may want to control what happens. For >> example: >> >> ``` >> let Optional.of(var contents) = optName >> else throw new IllegalArgumentException("name is empty"); >> ``` >> >> Having an `else` clause allows us to use a partial pattern, which receives >> control if the pattern does not match. The `else` clause could choose to >> throw, >> but could also choose to `break` or `return` to an enclosing context, or >> even >> recover by assigning the bindings. >> >> #### What about recovery? >> >> If we're supporting partial patterns, we might want to allow the `else` >> clause >> to provide defaults for the bindings, rather than throw. We can make the >> bindings of the >> pattern in the `let` statement be in scope, but definitely unassigned, in >> the >> `else` clause, which means the `else` clause could initialize them and >> continue: >> >> ``` >> let Optional.of(var contents) = optName >> else contents = "Unnamed"; >> ``` >> >> This allows us to continue, while preserving the invariant that when the >> `let` >> statement completes normally, all bindings are DA. >> >> #### What about guards >> >> If we're supporting partial patterns, we also need to consider the case >> where >> the pattern matches but we still want to reject the content. This could >> of >> course be handled by testing and throwing after the `let` completes, but >> if we >> want to recover via the `else` clause, we might want to handle this >> directly. >> We've already introduced a means to do this for switch cases -- a `when` >> clause >> -- and this works equally well in `let`: >> >> ``` >> let Point(var x, var y) = aPoint >> when x >= 0 && y >= 0 >> else { x = y = 0; } >> ``` >> >> #### What about expressions? >> >> The name `let` conjures up the image of `let` expressions in functional >> languages, where we introduce a local binding for use in the scope of a >> single >> expression. This is not an accident! It is quite useful when the same >> expression >> is going to be used multiple times, or when we want to limit the scope of >> a local >> to a specific computation. >> >> It is a short hop to `let` being usable as an expression, by providing an >> `in` >> clause: >> >> ``` >> String lastThree = >> let int len = s.length() >> in s.substring(len-3, len); >> ``` >> >> The scope of the binding `len` is the expression to the right of the `in`, >> nothing else. (As with `switch` expressions, the expression to the right >> of the `in` could be a block with a `yield` statement.) >> >> It is a further short hop to permitting _multiple_ matches in a single >> `let` >> statement or expression: >> >> ``` >> int area = let Point(var x0, var y0) = lowerLeft, >> Point(var x1, var y1) = upperRight >> in (x1-x0) * (y1-y0); >> ``` >> >> #### What about parameter bindings? >> >> Destructuring with total patterns is also useful for method and lambda >> parameters. For a lambda that accepts a `Point`, we could include the >> pattern >> in the lambda parameter list, and the bindings would automatically be in >> scope in the body. Instead of: >> >> ``` >> areaFn = (Point lowerLeft, Point upperRight) >> -> (upperRight.x() - lowerLeft.x()) * (upperRight.y() - >> lowerLeft.y()); >> ``` >> >> we could do the destructuring in the lambda header: >> >> ``` >> areaFn = (let Point(var x0, var y0) lowerLeft, >> let Point(var x1, var y1) upperRight) >> -> (x1-x0) * (y1-y0); >> ``` >> >> This allows us to treat the derived values to be "parameters" of the >> lambda. We >> would enforce totality at compile time, and dynamically reject remainder >> as we >> do with `switch` and `let` statements. >> >> I think this one may be a bridge too far, though. The method header >> should >> probably be reserved for API declaration, and destructuring only serves >> the >> implementation. I think I'd prefer to move the `let` into the body of the >> method or lambda. >> >> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: From amalloy at google.com Thu Apr 7 21:40:20 2022 From: amalloy at google.com (Alan Malloy) Date: Thu, 7 Apr 2022 14:40:20 -0700 Subject: [External] : Re: Remainder in pattern matching In-Reply-To: <54c16b85-2b48-c87c-306d-8eb9bce0968a@oracle.com> References: <54c16b85-2b48-c87c-306d-8eb9bce0968a@oracle.com> Message-ID: Yes, this clears up my concerns. On Fri, Apr 1, 2022 at 6:56 AM Brian Goetz wrote: > > It seems pretty hard to land anywhere other than where you've landed, for > most of this. I have the same sort of question as Dan: do we really want to > wrap exceptions thrown by other patterns? You say we want to discourage > patterns from throwing at all, and that's a lovely dream, but the behavior > of total patterns is to throw when they meet something in their remainder. > > > Not exactly. The behavior of *switch* is to throw when they meet > something in the remainder of *all their patterns*. For example: > > Box> bbs = new Box(null); > switch (bbs) { > case Box(Box(String s)): ... > case null, Box b: ... > } > > has no remainder and will not throw. Box(null) doesn't match the first > pattern, because when we unroll to what amounts to > > if (x instanceof Box alpha && alpha != null && alpha.value() > instanceof Box beta && beta != null) { > s = beta.value(); ... > } > else if (x == null || x instanceof Box) { ... } > > we never dereference something we don't know to be non-null. So Box(null) > doesn't match the first case, but the second case gets a shot at it. Only > if no case matches does switch throw; *pattern matching* should never > throw. (Same story with let, except its like a switch with one > putatively-exhaustive case.) > > Since user-defined patterns will surely involve primitive patterns at some > point, there is the possibility that one of those primitive patterns > throws, which bubbles up as an exception thrown by a user-defined pattern. > > > Again, primitive patterns won't throw, they just won't match. Under the > rules I outlined last time, if I have: > > Box b = new Box(null); > switch (b) { > case Box(int x): ... > ... > } > > when we try to match Box(int x) to Box(null), it will not NPE, it will > just not match, and we'll go on to the next case. If all cases don't > match, then the switch will throw ME, which is a failure of > *exhaustiveness*, not a failure in *pattern matching*. > > Does this change your first statement? > > > On Wed, Mar 30, 2022 at 7:40 AM Brian Goetz > wrote: > >> We should have wrapped this up a while ago, so I apologize for the late >> notice, but we really have to wrap up exceptions thrown from pattern >> contexts (today, switch) when an exhaustive context encounters a >> remainder. I think there's really one one sane choice, and the only thing >> to discuss is the spelling, but let's go through it. >> >> In the beginning, nulls were special in switch. The first thing is to >> evaluate the switch operand; if it is null, switch threw NPE. (I don't >> think this was motivated by any overt null hostility, at least not at >> first; it came from unboxing, where we said "if its a box, unbox it", and >> the unboxing throws NPE, and the same treatment was later added to enums >> (though that came out in the same version) and strings.) >> >> We have since refined switch so that some switches accept null. But for >> those that don't, I see no other move besides "if the operand is null and >> there is no null handling case, throw NPE." Null will always be a special >> remainder value (when it appears in the remainder.) >> >> In Java 12, when we did switch expressions, we had to confront the issue >> of novel enum constants. We considered a number of alternatives, and came >> up with throwing ICCE. This was a reasonable choice, though as it turns >> out is not one that scales as well as we had hoped it would at the time. >> The choice here is based on "the view of classfiles at compile time and run >> time has shifted in an incompatible way." ICCE is, as Kevin pointed out, a >> reliable signal that your classpath is borked. >> >> We now have two precedents from which to extrapolate, but as it turns >> out, neither is really very good for the general remainder case. >> >> Recall that we have a definition of _exhaustiveness_, which is, at some >> level, deliberately not exhaustive. We know that there are edge cases for >> which it is counterproductive to insist that the user explicitly cover, >> often for two reasons: one is that its annoying to the user (writing cases >> for things they believe should never happen), and the other that it >> undermines type checking (the most common way to do this is a default >> clause, which can sweep other errors under the rug.) >> >> If we have an exhaustive set of patterns on a type, the set of possible >> values for that type that are not covered by some pattern in the set is >> called the _remainder_. Computing the remainder exactly is hard, but >> computing an upper bound on the remainder is pretty easy. I'll say "x may >> be in the remainder of P* on T" to indicate that we're defining the upper >> bound. >> >> - If P* contains a deconstruction pattern P(Q*), null may be in the >> remainder of P*. >> - If T is sealed, instances of a novel subtype of T may be in the >> remainder of P*. >> - If T is an enum, novel enum constants of T may be in the remainder of >> P*. >> - If R(X x, Y y) is a record, and x is in the remainder of Q* on X, then >> `R(x, any)` may be in the remainder of { R(q) : q in Q*} on R. >> >> Examples: >> >> sealed interface X permits X1, X2 { } >> record X1(String s) implements X { } >> record X2(String s) implements X { } >> >> record R(X x1, X x2) { } >> >> switch (r) { >> case R(X1(String s), any): >> case R(X2(String s), X1(String s)): >> case R(X2(String s), X2(String s)): >> } >> >> This switch is exhaustive. Let N be a novel subtype of X. So the >> remainder includes: >> >> null, R(N, _), R(_, N), R(null, _), R(X2, null) >> >> It might be tempting to argue (in fact, someone has) that we should try >> to pick a "root cause" (null or novel) and throw that. But I think this is >> both excessive and unworkable. >> >> Excessive: This means that the compiler would have to enumerate the >> remainder set (its a set of patterns, so this is doable) and insert an >> extra synthetic clause for each. This is a lot of code footprint and >> complexity for a questionable benefit, and the sort of place where bugs >> hide. >> >> Unworkable: Ultimately such code will have to make an arbitrary choice, >> because R(N, null) and R(null, N) are in the remainder set. So which is >> the root cause? Null or novel? We'd have to make an arbitrary choice. >> >> >> So what I propose is the following simple answer instead: >> >> - If the switch target is null and no case handles null, throw NPE. (We >> know statically whether any case handles null, so this is easy and similar >> to what we do today.) >> - If the switch is an exhaustive enum switch, and no case handles the >> target, throw ICCE. (Again, we know statically whether the switch is over >> an enum type.) >> - In any other case of an exhaustive switch for which no case handles >> the target, we throw a new exception type, java.lang.MatchException, with >> an error message indicating remainder. >> >> The first two rules are basically dictated by compatibility. In >> hindsight, we might have not chosen ICCE in 12, and gone with the general >> (third) rule instead, but that's water under the bridge. >> >> We need to wrap this up in the next few days, so if you've concerns here, >> please get them on the record ASAP. >> >> >> As a separate but not-separate exception problem, we have to deal with at >> least two additional sources of exceptions: >> >> - A dtor / record acessor may throw an arbitrary exception in the course >> of evaluating whether a case matches. >> >> - User code in the switch may throw an arbitrary exception. >> >> For the latter, this has always been handled by having the switch >> terminate abruptly with the same exception, and we should continue to do >> this. >> >> For the former, we surely do not want to swallow this exception (such an >> exception indicates a bug). The choices here are to treat this the same >> way we do with user code, throwing it out of the switch, or to wrap with >> MatchException. >> >> I prefer the latter -- wrapping with MatchException -- because the >> exception is thrown from synthetic code between the user code and the >> ultimate thrower, which means the pattern matching feature is mediating >> access to the thrower. I think we should handle this as "if a pattern >> invoked from pattern matching completes abruptly by throwing X, pattern >> matching completes abruptly with MatchException", because the specific X is >> not a detail we want the user to bind to. (We don't want them to bind to >> anything, but if they do, we want them to bind to the logical action, not >> the implementation details.) >> >> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Thu Apr 7 21:54:24 2022 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 7 Apr 2022 23:54:24 +0200 (CEST) Subject: [External] : Re: Primitive type patterns In-Reply-To: References: <89faf208-d461-fcab-aac7-af260351db89@oracle.com> <1159934987.8907800.1649365852163.JavaMail.zimbra@u-pem.fr> Message-ID: <370267432.8917030.1649368464743.JavaMail.zimbra@u-pem.fr> > From: "Brian Goetz" > To: "Remi Forax" > Cc: "amber-spec-experts" > Sent: Thursday, April 7, 2022 11:24:05 PM > Subject: Re: [External] : Re: Primitive type patterns >> We already discussed those rules when we discuss instanceof, it means that "x >> instanceof primitive" has different meaning depending on the type of x > No, it does not. It means "does x match the pattern P" everywhere. It is pattern > P is that has different meanings depending on type. This may sound like a silly > distinction, but it is not! Pattern matching is inherently polymorphic -- it is > all about reflecting dynamic conversions statically -- and exhibits the *same* > polymorphism regardless of where it occurs. The switch is (runtime) polymorphic while each patterns does not have to be (statically) polymorphic. If you prefer, i'm okay with be a pattern overriding polymorphic but not with a pattern being overloading polymorphic (sometimes called ad-hoc polymorphism). Your are proposing overloading of patterns, i.e the same pattern having different meaning depending on the static types. > And, why would we not want duality with: > record R(short s) { } > ... > new R(x) because new R(x) is alone while case R(...) is part of a larger set of patterns/sub-pattern of the pattern matching, if for each pattern/sub-pattern, we need a double-entry table to understand the semantics, we are well past our complexity budget. R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From gavin.bierman at oracle.com Thu Apr 7 22:21:50 2022 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Thu, 7 Apr 2022 22:21:50 +0000 Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available In-Reply-To: <8eacf3a7-951f-e2a3-8ea2-de9230a9b514@oracle.com> References: <8eacf3a7-951f-e2a3-8ea2-de9230a9b514@oracle.com> Message-ID: <6CA55F62-49DF-4A2A-A7F8-910E8D69EB8A@oracle.com> On 7 Apr 2022, at 20:41, Brian Goetz > wrote: http://cr.openjdk.java.net/~gbierman/PatternSwitchPlusRecordPatterns/PatternSwitchPlusRecordPatterns-20220407/specs/patterns-switch-jls.html Comments welcome! The execution of an exhaustive switch can fail with a linkage error (an IncompatibleClassChangeError is thrown) if it encounters an instance of a permitted direct subclass that was not known at compile time (14.11.3, 15.28.2). Strictly speaking, the linkage error is not flagging a binary incompatible change of the sealed class, but more accurately a migration incompatible change of the sealed class. I think we should back away from ICCE here as well, and put this in the MatchException bucket too. Then: - a switch throws NPE if the operand is null; - an _enum switch_ throws ICCE when encountering a novel constant; - all other remainder errors are MatchException. File away for future use, that these clauses will have to be extended to include other exhaustive pattern-aware constructs, like let. 14.11.1 Switch Blocks The grammar for CaseOrDefaultLabel seems like it could be profitably refactored to reflect more of the restrictions: CaseOrDefaultLabel case (null | CaseConstant) {, CaseConstant } case [null, ] Pattern { WhenClause } case [null, ] default default and then you don't have to enumerate as many of the restrictions of what can combine with what. Will have a think about that. It is a compile-time error if a when expression has the value false. ... is a constant expression and has the value false ? Corrected. * A pattern case element p is switch compatible with T if p is assignable to type T (14.30.3). Isn't this cast-convertible? If the selector is String and the pattern is `Object o`, o is not assignable to String, but it is cast-convertible. Actually it?s a typo, it?s supposed to read ?applicable? instead of assignable! Corrected A switch label is said to dominate another switch label Can we say that in a pattern switch, default dominates everything, which has the effect of forcing the default to the bottom? We could do. I wasn?t sure that we had settled on that design - there was some discussion on this list. if there are values for which both apply and there is not an obvious preference Is this really what we mean? Don't we really mean that the first one matches everything the second one does? Corrected. A set of case elements is exhaustive This is a nit, but couldn't this be its own subsection? This section is getting long and varied. Let me have a think about that. T if T is downcast convertible to U Is this right? Upcast convertibility is OK too -- you can match `Object o` to a target of `String`, and vice versa. Corrected. If the type R is a raw type (4.8) then the type T must be a raw type, or vice versa; otherwise a compile-time error occurs. Is this the right restriction? What we want here (for this iteration) is that if R is generic, we specify the type parameters. But this is not the same thing. I would think we would want to say here something like "if the class of R is a generic class, R cannot be raw". Corrected. whose type names R missing a word Corrected. 1. A switch label that supports a pattern p applies if the value matches p (14.30.2). If pattern matching completes abruptly then determining which switch label applies completes abruptly for the same reason. I think this is carried over from the previous round? Or do we not resolve total type patterns to any at the top level of a switch? 1. If no case label matches but there is a default label, then the default label matches. If neither of these rules apply to any of the switch labels in the switch block, then a switch label that supports a default applies. Don't we need a clause that says "if there is no default, a MatchException is thrown"? If pattern matching completes abruptly then the process of determining which switch label applies completes abruptly for the same reason. Doesn't it complete abruptly with MatchException? Or can PM only complete abruptly with ME as well? I think perhaps you are misreading this section. This is just determining which label applies (none applies is a good answer too). The dealing with the consequences is taken care of in sections 14.11.3 for switch statements and 15.28.2 for switch expressions. A type pattern that does not appear as an element in a record component pattern list is called a top-level type pattern. For future: "or array component pattern list" Ack. The pattern variable declared by an any pattern has a type, which is a reference type. Is this still true? What if I have `record R(int x) {}` and `case R(var x)`? The type of x is not a reference type. Same for `case R(int x)`. Corrected. A pattern p is said to be unconditional at a type T if every value of type T will match p (after p has been resolved at type T (14.30.2)), and is defined as follows: An any pattern is unconditional at all T? So any patterns are not included in these compile-time analyses - they are an artefact of resolving patterns, which takes places afterwards. Thanks, Gavin -------------- next part -------------- An HTML attachment was scrubbed... URL: From guy.steele at oracle.com Fri Apr 8 02:23:03 2022 From: guy.steele at oracle.com (Guy Steele) Date: Fri, 8 Apr 2022 02:23:03 +0000 Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available In-Reply-To: References: Message-ID: On Apr 7, 2022, at 7:40 AM, Gavin Bierman > wrote: Dear experts: The first draft of a spec covering both the third preview of Pattern Matching for switch (JEP number coming, but currently available at https://openjdk.java.net/jeps/8282272) and JEP 405 (Record Patterns) is now available at: http://cr.openjdk.java.net/~gbierman/PatternSwitchPlusRecordPatterns/PatternSwitchPlusRecordPatterns-20220407/specs/patterns-switch-jls.html Comments welcome! Thanks, Gavin Also missing Oxford comma in first bullet of 6.3.2.6 switch Statements. 6.3.3.2 Record Pattern The following rule applies to a record pattern p: * For each pattern q in the record component pattern list of p, a pattern variable declared by q is definitely matched in the rest of record component pattern list. It is a compile-time error if a pattern variable declared by q is already in scope somewhere in the rest of the record component pattern list. Is the term ?rest of the list? carefully defined elsewhere? Otherwise it could be misconstrued as ?anywhere else in the list. Perhaps: ? For each pattern q in the record component pattern list of p, a pattern variable declared by q is definitely matched in every record pattern component that comes after it within the list. or ? For each pattern q in the record component pattern list of p, a pattern variable declared by q is definitely matched in every record pattern component that is to its right within the list. and similarly for the next sentence (?It is a compile-time error . . .?). -------------- next part -------------- An HTML attachment was scrubbed... URL: From guy.steele at oracle.com Fri Apr 8 02:27:57 2022 From: guy.steele at oracle.com (Guy Steele) Date: Fri, 8 Apr 2022 02:27:57 +0000 Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available In-Reply-To: References: Message-ID: 13.4.26 Evolution of Enum Classes Adding or reordering enum constants in an enum class will not break compatibility with pre-existing binaries. As with sealed classes (13.4.2.1), whilst adding an enum constant to an enum class is considered a binary compatible change, it may cause the execution of an exhaustive switch (14.11.1) to fail with a linkage error (an IncompatibleClassChangeError may be thrown) if the switch encounters the new enum constant that was not known at compile time (14.11.3, 15.28.2). Would it be more accurate (or more to the point) to replace ?whilst? with ?although?? ?Guy -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Apr 8 02:54:22 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 7 Apr 2022 22:54:22 -0400 Subject: [External] : Re: Primitive type patterns In-Reply-To: <370267432.8917030.1649368464743.JavaMail.zimbra@u-pem.fr> References: <89faf208-d461-fcab-aac7-af260351db89@oracle.com> <1159934987.8907800.1649365852163.JavaMail.zimbra@u-pem.fr> <370267432.8917030.1649368464743.JavaMail.zimbra@u-pem.fr> Message-ID: <6d3febc3-8f35-60f4-2e15-e963faa63b57@oracle.com> > > And, why would we not want duality with: > > ??? record R(short s) { } > ??? ... > ??? new R(x) > > > because new R(x) is alone while case R(...) is part of a larger set of > patterns/sub-pattern of the pattern matching, if for each > pattern/sub-pattern, we need a double-entry table to understand the > semantics, we are well past our complexity budget. > Again: it's the *same table* as all the other conversion contexts. To do something different here is what would be incremental complexity. Let me try this one more time, from a third perspective. What does `instanceof` do?? The only reasonable thing you would do after an `instanceof` is a cast.? Asking `instanceof` is asking, if I were to cast to this type, would I like the outcome?? Instanceof currently works only on reference types, and says no when (a) the cast would fail with CCE, and (b) if the operand is null, because, even though the cast would succeed, the next operation likely would not. Instanceof may be defined only on references now, but casting primitives is excruciatingly defined with the same full double-entry table that you don't like. So, when asking "what does a primitive type pattern mean", then, we can view this as the generalization of the same question: if I were to cast this target to this type, would I like the outcome?? The set of things that can go wrong (outcomes we wouldn't like) is slightly broader here; we can cast a long to int, and we'd get truncation, but that's a bad outcome, just like CCE.? If we interpret `instanceof` as the precondition test for a useful cast, we again get the same set of conversions: ?- I can safely cast a non-null Integer to int (unboxing with null check) ?- I can safely cast any int to Integer (boxing) ?- I can safely cast an int in the range of -128..127 to byte (narrowing with value range check) ?- I can safely cast any byte to int (widening) ?- etc. It's a third different explanation, and yet it still comes up with *exactly the same set of conversions* as the other two explanations (assignment and method invocation).? There's a reason all roads lead here; because the conversion sets are defined in a consistent manner.? Doing something *different* with pattern matching would be the new complexity. -------------- next part -------------- An HTML attachment was scrubbed... URL: From gavin.bierman at oracle.com Fri Apr 8 06:51:09 2022 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Fri, 8 Apr 2022 06:51:09 +0000 Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available In-Reply-To: References: Message-ID: Thanks Guy. All corrected. Gavin On 8 Apr 2022, at 03:27, Guy Steele > wrote: 13.4.26 Evolution of Enum Classes Adding or reordering enum constants in an enum class will not break compatibility with pre-existing binaries. As with sealed classes (13.4.2.1), whilst adding an enum constant to an enum class is considered a binary compatible change, it may cause the execution of an exhaustive switch (14.11.1) to fail with a linkage error (an IncompatibleClassChangeError may be thrown) if the switch encounters the new enum constant that was not known at compile time (14.11.3, 15.28.2). Would it be more accurate (or more to the point) to replace ?whilst? with ?although?? ?Guy -------------- next part -------------- An HTML attachment was scrubbed... URL: From maurizio.cimadamore at oracle.com Fri Apr 8 10:57:26 2022 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Fri, 8 Apr 2022 11:57:26 +0100 Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available In-Reply-To: References: Message-ID: <8ef7b21f-1303-991d-89fa-c32f95cd3f1a@oracle.com> Hi Gavin, great work - some comments: * in section 6 on names there's no mention of whether pattern variables are matched in the `when` clause. * section 14.11.1, you say that a case element is "unrefined" if it has `when` with constant expression whose value is `true`. This is a bit ambiguous: I think what you want here is "evaluates to true", because I think you also want to cover "true || true" or "!!true", right? I think that's what you meant, but the word "value" is confusing, I think. This ambiguity is also present in the rule below when we say that two case label with same "value" are not permitted. Maybe all this is pre-existing, but I wonder it it could be worth clarifying. * we define the concept of what it means for a case label to "support default" but we do not define what it means to "support null". The latter is also referred to in the list of checks for switch labels in 14.11.1 * "If a switch label appears at the end of a switch block, it is a compile-time error if it consists of more than one case or default label." - not sure I get this? I mean, sure I can't have "case default: case default" - but the rest? E.g. can't I end a switch with "case 4: case 5: ..." ? * this sentence "A switch label that supports a unrefined pattern p dominates another switch label supporting a pattern q if p dominates q" is tricky - but I think correct; only labels w/o a "when" can dominate other labels, and pattern dominance doesn't care about "when", so you shold be good. * 10.3.2 and also the small example at the end of the definition of "executable switch" refer to the notion of "any" pattern - but that pattern is not defined Maurizio On 07/04/2022 12:40, Gavin Bierman wrote: > Dear experts: > > The first draft of a spec covering both the third preview of Pattern > Matching for switch (JEP number coming, but currently available at > https://openjdk.java.net/jeps/8282272) and JEP 405 (Record Patterns) > is now available at: > > http://cr.openjdk.java.net/~gbierman/PatternSwitchPlusRecordPatterns/PatternSwitchPlusRecordPatterns-20220407/specs/patterns-switch-jls.html > > > Comments welcome! > > Thanks, > Gavin -------------- next part -------------- An HTML attachment was scrubbed... URL: From guy.steele at oracle.com Sun Apr 10 02:45:52 2022 From: guy.steele at oracle.com (Guy Steele) Date: Sun, 10 Apr 2022 02:45:52 +0000 Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available In-Reply-To: References: Message-ID: Nice work defining new terminology for Chapter 14. Just a few nits: 14.11.1 A switch label is said to support a default if either it has a default label, or it has a case label with a default. Maybe: A switch label is said to support a default if either it has a default label, or it has a case label with a default case element. Any variable that is used but not declared in a whenexpression must either be final or effectively final (4.12.4). Make that: Any variable that is used but not declared in a whenexpression must be either final or effectively final (4.12.4). The switch block of a switch statement or a switch expression is switch compatible with the type of the selector expression, T, if all the switch labels in the switch block are switch compatible with T. I suggest: The switch block of a switch statement or a switch expression is switch compatible with the type of the selector expression, T, if every switch label in the switch block is switch compatible with T. because the following sentence defines the phrase ?a switch label is switch compatible with T ? rather than ?(a set of) switch labels are switch compatible with T ?. (These are basic principles of the style of writing I tried to establish for JLS: (a) once you define a specific form for a technical phrase, stick as closely as possible to that form; (2) where possible, focus on a single item in preference to sets of items?this means that ?every? is usually a more useful quantifier than ?all?.) [more to come? I?m too sleepy to continue reading right now] ?Guy -------------- next part -------------- An HTML attachment was scrubbed... URL: From angelos.bimpoudis at oracle.com Thu Apr 14 21:34:13 2022 From: angelos.bimpoudis at oracle.com (Angelos Bimpoudis) Date: Thu, 14 Apr 2022 21:34:13 +0000 Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available In-Reply-To: References: Message-ID: Hello Gavin, Exceptional work. Some minor proposals from my side... 14.11.1 includes a discussion about executable switch expressions and statements. It describes the transformation of default and case labels after resolution of patterns. Maybe this deserves a separate subsection. > A set of case elements is exhaustive for a type T if it contains a pattern that is unconditional at type T (14.30.3) I think this is a crucial rule since it brings the two worlds of totality and exhaustivity together. It deserves a tiny example here. Do you think it would be beneficial? > The set Q is exhaustive from component d at type V, where d is the component following c, V is the type of corresponding component field in T, and Q is the set of patterns containing every record pattern in P whose component pattern corresponding to c covers U. Instead of using the word "covers" we need to talk about the T <: U subtype relation. Covers is a remnant of the previous terminology set IIUC (which was good, but here is undefined). nitpicking: a instanceof expression -> an a enum -> an enum a unrefined -> an unrefined and that statement can completely normally -> can complete normally then it is further tranformed -> then it is further *transformed* or the RelationalExpression of a instanceof -> an Many thanks, Aggelos ________________________________ From: amber-spec-experts on behalf of Gavin Bierman Sent: 07 April 2022 13:40 To: amber-spec-experts Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available Dear experts: The first draft of a spec covering both the third preview of Pattern Matching for switch (JEP number coming, but currently available at https://openjdk.java.net/jeps/8282272) and JEP 405 (Record Patterns) is now available at: http://cr.openjdk.java.net/~gbierman/PatternSwitchPlusRecordPatterns/PatternSwitchPlusRecordPatterns-20220407/specs/patterns-switch-jls.html Comments welcome! Thanks, Gavin -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Apr 15 20:50:27 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 15 Apr 2022 16:50:27 -0400 Subject: Evolving past reference type patterns Message-ID: <44d932f1-e9c0-0053-45b8-cd45a5a14c7c@oracle.com> We characterize patterns by their /applicability/ (static type checking), /unconditionality/ (can matching be determined without a dynamic check, akin to the difference between a static and dynamic cast), and /behavior/ (under what conditions does it match, and what bindings do we get.) Currently shipping As currently shipping, we have one kind of pattern: type patterns for reference types. We define the useful term ?downcast convertible? to mean there is a cast conversion that is not unchecked. So |Object| and |ArrayList| are downcast-convertible to each other, as are |List| and |ArrayList|, as are |List| and |ArrayList|, but not |List| to |ArrayList|. A type pattern |T t| for a ref type T is /applicable to/ a ref type U if U is downcast-convertible to T. A type pattern |T t| is /unconditional/ on |U| if |U <: T|. A type pattern |T t| matches a target x when the pattern is unconditional, or when |x instanceof T|; if so, its binding is |(T) x|. Record patterns In the next round, we will add /record patterns/, which bring in /nested patterns/. A record pattern |R(P*)| is applicable to a reference type U if U is downcast-convertible to R. A record pattern is never unconditional. A record pattern |R(P*)| matches a target |x| when |x instanceof R|, and when each component of |R| matches the corresponding nested pattern |P_i|. Matching against components is performed using the /instantiated/ static type of the component. Record patterns also drag in primitive patterns, because records can have primitive components. A primitive type pattern |P p| is applicable to, and unconditional on, the type P. A primitive type matches a target x when the pattern is unconditional, and its binding is |(P) x|. Record patterns also drag in |var| patterns as nested patterns. A |var| pattern is applicable to, and unconditional on, every type U, and its binding when matched to |x| whose static type is |U|, is |x| (think: identity conversion.) This is what we intend to specify for 19. Primitive patterns Looking ahead, we?ve talked about how far to extend primitive patterns beyond exact matches. While I know that this makes some people uncomfortable, I am still convinced that there is a more powerful role for patterns to play here, and that is: as the cast precondition. A language that has casts but no way to ask ?would this cast succeed? is deficient; either casts will not be used, or we would have to tolerate cast failure, manifesting as either exceptions or data loss / corruption. (One could argue that for primitive casts, Java is deficient in this way now (you can make a lossy cast from long to int), but the monomorphic nature of primitive types mitigates this somewhat.) Prior to patterns, users have internalized that before a cast, you should first do an |instanceof| to the same type. For reference types, the |instanceof| operator is the ?cast precondition? operator, with an additional (sensible) opinion that |null| is not deemed to be an instance of anything, because even if the cast were to succeed, the result would be unlikely to be usable as the target type. There are many types that can be cast to |int|, at least under some conditions: * Integer, except null * byte, short, and char, unconditionally * Byte, Short, and Character, except null * long, but with potential loss of precision * Object or Number, if it?s not null and is an Integer Just as |instanceof T| for a reference type T tells us whether a cast to T would profitably succeed, we can define |instanceof int| the same way: whether a cast to int would succeed without error or loss of precision. By this measure, |instanceof int| would be true for: * any int * Integer, when the instance is non-null (unboxing) * Any reference type that is cast-convertible to Integer, and is |instanceof Integer| (unboxing) * byte, short, and char, unconditionally (types that can be widened to int) * Byte, Short, and Character, when non-null (unboxing plus widening) * long when in the range of int (narrowing) * Long when non-null, and in the range of int (unboxing plus narrowing) This table can be generated simply by looking at the set of cast conversions ? and we haven?t talked about patterns yet. This is simply the generalization of |instanceof| to primitives. If we are to allow |instanceof int| at all, I don?t think there is really any choice of what it means. And this is useful in the language we have today, separate from patterns: * asking if something fits in the range of a byte or int; doing this by hand is annoying and error-prone * asking if casting from long to int would produce truncation; doing this by hand is annoying and error-prone Doing this means that |if (x instanceof T) ... (T) x ... | becomes universally meaningful, and captures exactly the preconditions for when the cast succeeds without error, loss of precision, or null escape. (And as Valhalla is going to bring primitives more into the world of objects, generalizing this relationship will become only more important.) And if we?ve given meaning to |instanceof int|, it is hard to see how the pattern |int x| could behave any differently than |instanceof int|, because otherwise, we could not refactor the above idiom to: |if (x instanceof T t) ... t ... | Extending instanceof / pattern matching to primitives in this way is not only a sensible generalization, but failing to do so would expose gratuitous asymmetries that would be impediments to refactoring: * Cannot necessarily refactor |int x = 0| with |let int x = 0|. While this may seem non-problematic on the surface, as soon as |let| acquires any other feature besides ?straight unconditional pattern assignment?, such as let-expression, it puts users in the bad choice between ?Can use let, or can use assignment conversion, but not both.? * Loss of duality between |new X(args)| and |case X(ARGS)|. The duality between construction and deconstruction patterns (and similar for static factories/patterns, builders/?unbuilders?, and collection literals/patterns is a key part of the story; we take things apart in the same way we put them together. Any gratuitous divergence becomes an avoidable sharp edge. Since these are related to assignment and method invocation, let?s ask: how do these conversions line up with assignment and method invocation conversions? There are two main differences between the safe cast conversions and assignment context. One has to do with narrowing; the ?if it?s a literal and in range? is the best approximation that assignment can do, while in a context that accepts partial patterns, the pattern can be more discriminating, and so should. The other is treatment of null; again, because of the totality requirement, assignment throws when unboxing a null, but pattern matching in a partial context can deal more gracefully, and simply decline to match. There are also some small differences between the safe cast conversions and method invocation context. There is the same issue with unboxing null (throws in (loose) invocation context), and method invocation context makes no attempt to do narrowing, even for literals. This last seems mostly a historical wart, which now can?t be changed because it would either potentially change (very few) overload selection choices, or would require another stage of selection. What are the arguments against this interpretation? They seem to be various flavors of ?ok, but, do we really need this?? and ?yikes, new complexity.? The first argument comes from a desire to treat pattern matching as a ?Coin?-like feature, strictly limiting its scope. (As an example of a similar kind of pushback, in the early days, it was asked ?but does pattern matching have to be an expression, couldn?t we just have an ?ifmatch? statement? (See answer here: http://mail.openjdk.java.net/pipermail/amber-dev/2018-December/003842.html) This is the sort of question we get a lot ? there?s a natural tendency to try to ?scope down? features that seem unfamiliar. But I think it?s counterproductive here. The second argument is largely a red herring, in that this is /not/ new complexity, since these are exactly the rules for successful casts. In fact, not doing it might well be perceived as new complexity, since it results in more corner cases where refactorings that seem like they should work, do not, because of conversions. ? -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Apr 15 21:36:26 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 15 Apr 2022 17:36:26 -0400 Subject: Evolving past reference type patterns In-Reply-To: <44d932f1-e9c0-0053-45b8-cd45a5a14c7c@oracle.com> References: <44d932f1-e9c0-0053-45b8-cd45a5a14c7c@oracle.com> Message-ID: > * asking if something fits in the range of a byte or int; doing this > by hand is annoying and error-prone > * asking if casting from long to int would produce truncation; doing > this by hand is annoying and error-prone > > Here?s some real code I wrote recently that would benefit dramatically from this: |default CodeBuilder constantInstruction(int value) { return with(switch (value) { case -1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_M1); case 0 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_0); case 1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_1); case 2 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_2); case 3 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_3); case 4 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_4); case 5 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_5); default -> { if (value >= -128 && value <= 127) { yield ConstantInstruction.ofArgument(Opcode.BIPUSH, value); } else if (value >= -32768 && value <= 32767) { yield ConstantInstruction.ofArgument(Opcode.SIPUSH, value); } else { yield ConstantInstruction.ofLoad(Opcode.LDC, BytecodeHelpers.constantEntry(constantPool(), value)); } } }); } | could become the less error-prone and uniform: |default CodeBuilder constantInstruction(int value) { return with(switch (value) { case -1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_M1); case 0 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_0); case 1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_1); case 2 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_2); case 3 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_3); case 4 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_4); case 5 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_5); case byte value -> ConstantInstruction.ofArgument(Opcode.BIPUSH, value); case short value -> ConstantInstruction.ofArgument(Opcode.SIPUSH, value); default -> ConstantInstruction.ofLoad(Opcode.LDC, BytecodeHelpers.constantEntry(constantPool(), value)); }); } | ? -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Fri Apr 15 22:13:29 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Sat, 16 Apr 2022 00:13:29 +0200 (CEST) Subject: Evolving past reference type patterns In-Reply-To: <44d932f1-e9c0-0053-45b8-cd45a5a14c7c@oracle.com> References: <44d932f1-e9c0-0053-45b8-cd45a5a14c7c@oracle.com> Message-ID: <1111896893.13444109.1650060809553.JavaMail.zimbra@u-pem.fr> > From: "Brian Goetz" > To: "amber-spec-experts" > Sent: Friday, April 15, 2022 10:50:27 PM > Subject: Evolving past reference type patterns [...] > There are also some small differences between the safe cast conversions and > method invocation context. There is the same issue with unboxing null (throws > in (loose) invocation context), and method invocation context makes no attempt > to do narrowing, even for literals. This last seems mostly a historical wart, > which now can?t be changed because it would either potentially change (very > few) overload selection choices, or would require another stage of selection. It's not an historical wart, it's careful design (or co-design between the language and the VM). Assignments can be used for fields, that are stored on heap, while method arguments can only be stored on stack. Values on stack/in registers are either 32 bits or 64 bits while values on the heap are multiple of 8 bits. That's the same reason why the result of the addition of two shorts is an int and why assignment conversions in the context of a computation (the pattern matching helps to express computation) feels weird. > What are the arguments against this interpretation? They seem to be various > flavors of ?ok, but, do we really need this?? and ?yikes, new complexity.? > The first argument comes from a desire to treat pattern matching as a > ?Coin?-like feature, strictly limiting its scope. (As an example of a similar > kind of pushback, in the early days, it was asked ?but does pattern matching > have to be an expression, couldn?t we just have an ?ifmatch? statement? (See > answer here: [ > http://mail.openjdk.java.net/pipermail/amber-dev/2018-December/003842.html | > http://mail.openjdk.java.net/pipermail/amber-dev/2018-December/003842.html ] ) > This is the sort of question we get a lot ? there?s a natural tendency to try > to ?scope down? features that seem unfamiliar. But I think it?s > counterproductive here. We all know that this is a big feature, don't worry. > The second argument is largely a red herring, in that this is not new > complexity, since these are exactly the rules for successful casts. In fact, > not doing it might well be perceived as new complexity, since it results in > more corner cases where refactorings that seem like they should work, do not, > because of conversions. > ? It is new complexity, the rules for primitive narrowing are brand new. Can you provides examples of such refactorings ? R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Apr 15 22:25:20 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 15 Apr 2022 18:25:20 -0400 Subject: [External] : Re: Evolving past reference type patterns In-Reply-To: <1111896893.13444109.1650060809553.JavaMail.zimbra@u-pem.fr> References: <44d932f1-e9c0-0053-45b8-cd45a5a14c7c@oracle.com> <1111896893.13444109.1650060809553.JavaMail.zimbra@u-pem.fr> Message-ID: <5521e24e-8591-8127-217c-9b2e5a68a93f@oracle.com> > ? > Can you provides examples of such refactorings ? > Refactoring ??? int x = aShort; ??? foo(x, x); to ??? let int x = aShort ??? in foo(x, x); -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Apr 15 22:27:25 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 15 Apr 2022 18:27:25 -0400 Subject: [External] : Re: Evolving past reference type patterns In-Reply-To: <5521e24e-8591-8127-217c-9b2e5a68a93f@oracle.com> References: <44d932f1-e9c0-0053-45b8-cd45a5a14c7c@oracle.com> <1111896893.13444109.1650060809553.JavaMail.zimbra@u-pem.fr> <5521e24e-8591-8127-217c-9b2e5a68a93f@oracle.com> Message-ID: <9cafe58c-53c0-5b1b-e8a6-9d0856613e30@oracle.com> Also, the following would be an error, even though the two are naturally dual: record Foo(int x) { } Foo f = new Foo(aShort); if (f instanceof Foo(short x)) { ... }? // would be an error without `short x` applicable to int On 4/15/2022 6:25 PM, Brian Goetz wrote: > >> ? >> Can you provides examples of such refactorings ? >> > > Refactoring > > ??? int x = aShort; > ??? foo(x, x); > > to > > ??? let int x = aShort > ??? in foo(x, x); > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From guy.steele at oracle.com Sat Apr 16 02:10:06 2022 From: guy.steele at oracle.com (Guy Steele) Date: Sat, 16 Apr 2022 02:10:06 +0000 Subject: Evolving past reference type patterns In-Reply-To: References: <44d932f1-e9c0-0053-45b8-cd45a5a14c7c@oracle.com> Message-ID: <49E3451F-0AF6-40B2-95FE-77F9D5988F61@oracle.com> Yes, this is a clear improvement to the example code. That said, I am always (or at least now) a bit leery of language designers motivating a new language feature by pointing out that it would make a compiler easier to write. As I have learned the hard way on more than one language project, compilers are not always representative of typical application code. (Please consider this remark as only very minor pushback on the form of the argument.) On Apr 15, 2022, at 5:36 PM, Brian Goetz > wrote: * asking if something fits in the range of a byte or int; doing this by hand is annoying and error-prone * asking if casting from long to int would produce truncation; doing this by hand is annoying and error-prone Here?s some real code I wrote recently that would benefit dramatically from this: default CodeBuilder constantInstruction(int value) { return with(switch (value) { case -1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_M1); case 0 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_0); case 1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_1); case 2 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_2); case 3 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_3); case 4 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_4); case 5 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_5); default -> { if (value >= -128 && value <= 127) { yield ConstantInstruction.ofArgument(Opcode.BIPUSH, value); } else if (value >= -32768 && value <= 32767) { yield ConstantInstruction.ofArgument(Opcode.SIPUSH, value); } else { yield ConstantInstruction.ofLoad(Opcode.LDC, BytecodeHelpers.constantEntry(constantPool(), value)); } } }); } could become the less error-prone and uniform: default CodeBuilder constantInstruction(int value) { return with(switch (value) { case -1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_M1); case 0 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_0); case 1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_1); case 2 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_2); case 3 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_3); case 4 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_4); case 5 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_5); case byte value -> ConstantInstruction.ofArgument(Opcode.BIPUSH, value); case short value -> ConstantInstruction.ofArgument(Opcode.SIPUSH, value); default -> ConstantInstruction.ofLoad(Opcode.LDC, BytecodeHelpers.constantEntry(constantPool(), value)); }); } ? -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Sat Apr 16 08:04:37 2022 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sat, 16 Apr 2022 10:04:37 +0200 (CEST) Subject: [External] : Re: Evolving past reference type patterns In-Reply-To: <5521e24e-8591-8127-217c-9b2e5a68a93f@oracle.com> References: <44d932f1-e9c0-0053-45b8-cd45a5a14c7c@oracle.com> <1111896893.13444109.1650060809553.JavaMail.zimbra@u-pem.fr> <5521e24e-8591-8127-217c-9b2e5a68a93f@oracle.com> Message-ID: <131197452.13478433.1650096277686.JavaMail.zimbra@u-pem.fr> > From: "Brian Goetz" > To: "Remi Forax" > Cc: "amber-spec-experts" > Sent: Saturday, April 16, 2022 12:25:20 AM > Subject: Re: [External] : Re: Evolving past reference type patterns >>> ? >>> Can you provides examples of such refactorings ? > Refactoring > int x = aShort; > foo(x, x); > to > let int x = aShort > in foo(x, x); We already agree that pattern assignment (the let =) should use assignment conversions. R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Sat Apr 16 08:18:28 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Sat, 16 Apr 2022 10:18:28 +0200 (CEST) Subject: Evolving past reference type patterns In-Reply-To: <49E3451F-0AF6-40B2-95FE-77F9D5988F61@oracle.com> References: <44d932f1-e9c0-0053-45b8-cd45a5a14c7c@oracle.com> <49E3451F-0AF6-40B2-95FE-77F9D5988F61@oracle.com> Message-ID: <858761574.13498838.1650097108990.JavaMail.zimbra@u-pem.fr> > From: "Guy Steele" > To: "Brian Goetz" > Cc: "amber-spec-experts" > Sent: Saturday, April 16, 2022 4:10:06 AM > Subject: Re: Evolving past reference type patterns > Yes, this is a clear improvement to the example code. > That said, I am always (or at least now) a bit leery of language designers > motivating a new language feature by pointing out that it would make a compiler > easier to write. As I have learned the hard way on more than one language > project, compilers are not always representative of typical application code. > (Please consider this remark as only very minor pushback on the form of the > argument.) This is a very specific example due to the way integers are encoded in the bytecode, also you can simplify the code a bit more because ICONST_M1, ICONST_0, etc are all subsequents. Moreover, as i said earlier, it's more a work for method patterns. If we suppose we have a static pattern method isByte in java.lang.Byte class Byte { static pattern (byte) isByte(int value) { if (value >= -128 && value <= 127) { return match (short) value; } return no-match; } } the code becomes return with(switch (value) { case -1 .. 5 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_M1 + value); case Byte.isByte(byte _) -> ConstantInstruction.ofArgument(Opcode.BIPUSH, value); case Short.isShort(short _) -> ConstantInstruction.ofArgument(Opcode.SIPUSH, value); default -> ConstantInstruction.ofLoad(Opcode.LDC, BytecodeHelpers.constantEntry(constantPool(), value)); }); R?mi >> On Apr 15, 2022, at 5:36 PM, Brian Goetz < [ mailto:brian.goetz at oracle.com | >> brian.goetz at oracle.com ] > wrote: >>> * asking if something fits in the range of a byte or int; doing this by hand is >>> annoying and error-prone >>> * asking if casting from long to int would produce truncation; doing this by >>> hand is annoying and error-prone >> Here?s some real code I wrote recently that would benefit dramatically from >> this: >> default CodeBuilder constantInstruction(int value) { >> return with(switch (value) { >> case -1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_M1); >> case 0 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_0); >> case 1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_1); >> case 2 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_2); >> case 3 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_3); >> case 4 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_4); >> case 5 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_5); >> default -> { >> if (value >= -128 && value <= 127) { >> yield ConstantInstruction.ofArgument(Opcode.BIPUSH, value); >> } >> else if (value >= -32768 && value <= 32767) { >> yield ConstantInstruction.ofArgument(Opcode.SIPUSH, value); >> } >> else { >> yield ConstantInstruction.ofLoad(Opcode.LDC, >> BytecodeHelpers.constantEntry(constantPool(), value)); >> } >> } >> }); >> } >> could become the less error-prone and uniform: >> default CodeBuilder constantInstruction(int value) { >> return with(switch (value) { >> case -1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_M1); >> case 0 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_0); >> case 1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_1); >> case 2 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_2); >> case 3 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_3); >> case 4 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_4); >> case 5 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_5); >> case byte value -> ConstantInstruction.ofArgument(Opcode.BIPUSH, value); >> case short value -> ConstantInstruction.ofArgument(Opcode.SIPUSH, value); >> default -> ConstantInstruction.ofLoad(Opcode.LDC, >> BytecodeHelpers.constantEntry(constantPool(), value)); >> }); >> } >> ? -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Sat Apr 16 12:57:34 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Sat, 16 Apr 2022 14:57:34 +0200 (CEST) Subject: case null vs null pattern Message-ID: <94660367.13513595.1650113854003.JavaMail.zimbra@u-pem.fr> Hi all, i maybe wrong but it seems that the spec consider null as a kind of case instead of as a kind of pattern, which disables the refactoring that should be possible with the introduction of the record pattern. Let suppose i have a sealed type with only one implementation declared like this sealed interface I { record A() implements I { } } if null is part of the set of possible values, i have switches like this switch(i) { case null, A a -> // do something with 'a' here } Now we are introducing record pattern into the mix, so i can have a Box of I, record Box(I i) { } the problem is that i can not write switch(box) { case Box(A|null a) -> // do something with 'a' here } because null is handled as a kind of case instead of as a kind of a null pattern. Should we introduce a null pattern instead of having a specific "case null" ? (and disallow the null pattern in an instanceof ?) regards, R?mi From brian.goetz at oracle.com Sat Apr 16 15:34:55 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 16 Apr 2022 15:34:55 +0000 Subject: Evolving past reference type patterns In-Reply-To: <49E3451F-0AF6-40B2-95FE-77F9D5988F61@oracle.com> References: <44d932f1-e9c0-0053-45b8-cd45a5a14c7c@oracle.com> <49E3451F-0AF6-40B2-95FE-77F9D5988F61@oracle.com> Message-ID: <76B967B4-D426-41F3-84A4-5A40628A4983@oracle.com> > On Apr 15, 2022, at 10:10 PM, Guy Steele wrote: > > That said, I am always (or at least now) a bit leery of language designers motivating a new language feature by pointing out that it would make a compiler easier to write. As I have learned the hard way on more than one language project, compilers are not always representative of typical application code. (Please consider this remark as only very minor pushback on the form of the argument.) Indeed, this is something to be vigilant for. In fact, one could make this observation about pattern matching in entirety! Pattern matching is a feature that all compiler writers love, because compilers are mostly just big tree-transforming machines, and so *of course* compiler writers see it as a way to make their lives easier. (Obviously other programmers besides compiler writers like it too.) So, let me remind everyone why we are doing pattern matching, and it is not just ?they?re better than visitors.? We may be doing this incrementally, but there?s a Big Picture motivation here, let me try to tell some more of it. Due to trends in hardware and software development practices, programs are getting smaller. It may be a cartoonish exaggeration to say that monoliths are being replaced by microservices, but the fact remains: units of deployment are getting smaller, because we?ve discovered that breaking things up into smaller units with fewer responsibilities offers us more flexibility. Geometrically, when you shrink a region, the percentage of that region that is close to the boundary goes up. And so a natural consequence of this trend towards smaller deployment units is that more code is close to the boundary with the outside world, and will want to exchange data with the outside world. In the Monolith days, ?data from the outside world? was as likely as not to be a serialized Java object, but today, it is likely to be a blob of JSON or XML or YAML or a database result set. And this data is at best untyped relative to Java?s type system. (JSON has integers, but they?re not constrained to the range of Java?s int, etc.) At the boundary, we have to deal with all sorts of messy stuff: IO errors, bad data, etc. But Java developers want to represent data using clean, statically typed objects with representational invariants. In a Big Monolith, where most of the code is in the interior, it was slightly more tolerable to have big piles of conversion code at the boundary. But when all the code lives a short hop from the boundary, our budget for adaptation to a more amenable format is smaller. Records and sealed types let us define ad-hoc domain models; pattern matching lets us define polymorphism over those ad-hoc data models, as well as more general ad-hoc polymorphism. Records, sealed types, and pattern matching let us adapt over the impedance mismatch between Java?s type system and messy stuff like JSON, at a cost we are all willing to pay. And it extends beyond simple patterns like we?ve seen so far; the end goal of this exercise is to use pattern matching to decompose complex entities like JSON blobs in a compositional manner ? essentially defining the data boundary in one swoop, like an adapter that is JSON-shaped on one side and Java-shaped on the other. (We obviously have a long way to go to get there.) From brian.goetz at oracle.com Sat Apr 16 15:50:26 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 16 Apr 2022 15:50:26 +0000 Subject: case null vs null pattern In-Reply-To: <94660367.13513595.1650113854003.JavaMail.zimbra@u-pem.fr> References: <94660367.13513595.1650113854003.JavaMail.zimbra@u-pem.fr> Message-ID: This is correct; I agree this is ?not quite where we want to be yet?, but the path to get there is not obvious, which is why we haven?t proposed anything more than we have. At some level (though this isn?t the whole story), the ?null pattern? is in the same limbo as constant patterns. Constant case labels are currently still just case labels, not patterns, so you can say `case 1` but cannot yet say `case Foo(1)`. The semantics of constant patterns are trivial, but we?re still not sure what the best way to surface it is. (This is mostly a syntax bike shed, but I don?t want to paint it now.) So when constant patterns come to the top of the priority list, null will be waiting there along with ?foo? and 42. Still, that doesn?t get us out of the woods; we can express ?Foo or null? with a binding in switches, but not in instanceof. (Truly, null is the gift that keeps on giving.) Maybe the way out of that is nullable type patterns (`T?`), but we?re not ready to go there yet either. In the meantime, nulls in switch can avail themselves of the same workaround that other constant patterns do: `case Foo(var x) when x == null`. > On Apr 16, 2022, at 8:57 AM, Remi Forax wrote: > > Hi all, > i maybe wrong but it seems that the spec consider null as a kind of case instead of as a kind of pattern, which disables the refactoring that should be possible with the introduction of the record pattern. > > Let suppose i have a sealed type with only one implementation declared like this > > sealed interface I { > record A() implements I { } > } > > if null is part of the set of possible values, i have switches like this > > switch(i) { > case null, A a -> // do something with 'a' here > } > > > Now we are introducing record pattern into the mix, so i can have a Box of I, > record Box(I i) { } > > the problem is that i can not write > switch(box) { > case Box(A|null a) -> // do something with 'a' here > } > > because null is handled as a kind of case instead of as a kind of a null pattern. > > Should we introduce a null pattern instead of having a specific "case null" ? > (and disallow the null pattern in an instanceof ?) > > regards, > R?mi From forax at univ-mlv.fr Sun Apr 17 09:48:50 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Sun, 17 Apr 2022 11:48:50 +0200 (CEST) Subject: Record pattern and side effects Message-ID: <448085588.13668630.1650188930595.JavaMail.zimbra@u-pem.fr> This is something i think we have no discussed, with a record pattern, the switch has to call the record accessors, and those can do side effects, revealing the order of the calls to the accessors. So by example, with a code like this record Foo(Object o1, Object o2) { public Object o2() { throw new AssertionError(); } } void int m(Foo foo) { return switch(foo) { case Foo(String s, Object o2) -> 1 case Foo foo -> 2 }; } m(new Foo(3, 4)); // throw AssertionError ? Do the call throw an AssertionError ? I believe the answer is no, because 3 is not a String, so Foo::o2() is not called. R?mi From brian.goetz at oracle.com Sun Apr 17 14:58:26 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Sun, 17 Apr 2022 14:58:26 +0000 Subject: Record pattern and side effects In-Reply-To: <448085588.13668630.1650188930595.JavaMail.zimbra@u-pem.fr> References: <448085588.13668630.1650188930595.JavaMail.zimbra@u-pem.fr> Message-ID: <2897C3E9-8DD2-49AE-9421-479753BB51A5@oracle.com> Yes, this is something we have to get ?on the record?. Record patterns are a special case of deconstruction patterns; in general, we will invoke the deconstructor (which is some sort of imperative code) as part of the match, which may have side-effects or throw exceptions. With records, we go right to the accessors, but its the same game, so I?ll just say ?invoke the deconstructor? to describe both. While we can do what we can to discourage side-effects in deconstructors, they will happen. This raises all sorts of questions about what flexibility the compiler has. Q: if we have case Foo(Bar(String s)): case Foo(Bar(Integer i)): must we call the Foo and Bar deconstructors once, twice, or ?dealer?s choice?? (I know you like the trick of factoring a common head, and this is a good trick, but it doesn?t answer the general question.) Q: To illustrate the limitations of the ?common head? trick, if we have case Foo(P, Bar(String s)): case Foo(Q, Bar(String s)): can we factor a common ?tail?, where we invoke Foo and Bar just once, and then use P and Q against the first binding? Q: What about reordering? If we have disjoint patterns, can we reorder: case Foo(Bar x): case TypeDisjointWithFoo t: case Foo(Baz x): into case Foo(Bar x): case Foo(Baz x): case TypeDisjointWithFoo t: and then fold the head, so we only invoke the Foo dtor once? Most of the papers about efficient pattern dispatch are relatively little help on this front, because the come with the assumption of purity / side-effect-freedom. But it seems obvious that if we were trying to optimize dispatch, our cost model would be something like arithmetic op << type test << dtor invocation, and so we?d want to optimize for minimizing dtor invocations where we can. We?ve already asked one of the questions on side effects (though not sure we agreed on the answer): what if the dtor throws? The working story is that the exception is wrapped in a MatchException. (I know you don?t like this, but let?s not rehash the same arguments.) But, exceptions are easier than general side effects because you can throw at most one exception before we bail out; what if your accessor increments some global state? Do we specify a strict order of execution? You are appealing to a left-to-right constraint; this is a reasonable thing to consider, but surely not the only path. But I think its a lower-order bit; the higher-order bit is whether we are allowed to, or required to, fold multiple dtor invocations into one, and similarly whether we are allowed to reorder disjoint cases. One consistent rule is that we are not allowed to reorder or optimize anything, and do everything strictly left-to-right, top-to-bottom. That would surely be a credible answer, and arguably the answer that builds on how the language works today. But I don?t like it so much, because it means we give up a lot of optimization ability for something that should never happen. (This relates to a more general question (again, not here) of whether a dtor / declared pattern is more like a method (as in Scala, returning Option[Tuple]) or ?something else?. The more like a method we tell people it is, the more pattern evaluation will feel like method invocation, and the more constrained we are to do things strictly top-to-bottom, left-to-right.) Alternately, we could let the language have freedom to ?cache? the result of partial matches, where if we learned, in a previous case, that x matches Foo(STUFF), we can reuse that judgment. And we can go further, to require the language to cache in some cases and not in others (I know this is your preferred answer.) > On Apr 17, 2022, at 5:48 AM, Remi Forax wrote: > > This is something i think we have no discussed, with a record pattern, the switch has to call the record accessors, and those can do side effects, > revealing the order of the calls to the accessors. > > So by example, with a code like this > > record Foo(Object o1, Object o2) { > public Object o2() { > throw new AssertionError(); > } > } > > void int m(Foo foo) { > return switch(foo) { > case Foo(String s, Object o2) -> 1 > case Foo foo -> 2 > }; > } > > m(new Foo(3, 4)); // throw AssertionError ? > > Do the call throw an AssertionError ? > I believe the answer is no, because 3 is not a String, so Foo::o2() is not called. > > R?mi From forax at univ-mlv.fr Mon Apr 18 22:49:58 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 19 Apr 2022 00:49:58 +0200 (CEST) Subject: case null / null pattern (v2) Message-ID: <1527944420.13948964.1650322198675.JavaMail.zimbra@u-pem.fr> I've found a way to encode the null pattern if you have a record record Foo(int x) { } Foo foo = ... return switch(foo) { case Foo(int _) foo -> "i'm a foo not null here !"; case Foo fooButNull -> "i can be only null here !"; }; I wonder if allowing those two patterns, a record pattern and a type pattern using the same type is a good idea or not, it seems a great way to obfuscate thing. R?mi From brian.goetz at oracle.com Tue Apr 19 20:02:22 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 19 Apr 2022 16:02:22 -0400 Subject: case null / null pattern (v2) In-Reply-To: <1527944420.13948964.1650322198675.JavaMail.zimbra@u-pem.fr> References: <1527944420.13948964.1650322198675.JavaMail.zimbra@u-pem.fr> Message-ID: <5a13974d-c61b-a479-e1b3-4e582e7fb4cd@oracle.com> With the currently specified semantics, the second pattern is dead, because switches will only match null at the top level with a case null.? This was an accommodation to clarify that that the null-hostility of switch is a property of switch, not patterns, and make it more clear when switch will NPE. Regardless, what you're asking for is a more precise remainder checking.? The first pattern matches all non-null Foo; because no case matches null, you're asking that we recognize that there is a dominance relationship here.? This is reasonable to consider (though harder, because null makes everything harder.) On 4/18/2022 6:49 PM, Remi Forax wrote: > I've found a way to encode the null pattern if you have a record > > record Foo(int x) { } > > Foo foo = ... > return switch(foo) { > case Foo(int _) foo -> "i'm a foo not null here !"; > case Foo fooButNull -> "i can be only null here !"; > }; > > I wonder if allowing those two patterns, a record pattern and a type pattern using the same type is a good idea or not, it seems a great way to obfuscate thing. > > R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From gavin.bierman at oracle.com Wed Apr 20 15:44:22 2022 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Wed, 20 Apr 2022 15:44:22 +0000 Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available In-Reply-To: <8ef7b21f-1303-991d-89fa-c32f95cd3f1a@oracle.com> References: <8ef7b21f-1303-991d-89fa-c32f95cd3f1a@oracle.com> Message-ID: Thanks Maurizio! On 8 Apr 2022, at 11:57, Maurizio Cimadamore > wrote: Hi Gavin, great work - some comments: * in section 6 on names there's no mention of whether pattern variables are matched in the `when` clause. This is covered in section 6.3.4? But maybe I misunderstand your point? * section 14.11.1, you say that a case element is "unrefined" if it has `when` with constant expression whose value is `true`. This is a bit ambiguous: I think what you want here is "evaluates to true", because I think you also want to cover "true || true" or "!!true", right? I think that's what you meant, but the word "value" is confusing, I think. This ambiguity is also present in the rule below when we say that two case label with same "value" are not permitted. Maybe all this is pre-existing, but I wonder it it could be worth clarifying. Point taken, but I followed the language of Chapter of JLS on Definite Assignment, e.g. in 16.1.1: "V is [un]assigned after any >>> constant expression (?15.29) whose value is true <<< when false.? * we define the concept of what it means for a case label to "support default" but we do not define what it means to "support null". The latter is also referred to in the list of checks for switch labels in 14.11.1 Yeah - I?m probably trying to be too clever here...let me try to clarify some of the language... * "If a switch label appears at the end of a switch block, it is a compile-time error if it consists of more than one case or default label." - not sure I get this? I mean, sure I can't have "case default: case default" - but the rest? E.g. can't I end a switch with "case 4: case 5: ..." ? Yikes - mistake! Thanks for spotting. * this sentence "A switch label that supports a unrefined pattern p dominates another switch label supporting a pattern q if p dominates q" is tricky - but I think correct; only labels w/o a "when" can dominate other labels, and pattern dominance doesn't care about "when", so you shold be good. * 10.3.2 and also the small example at the end of the definition of "executable switch" refer to the notion of "any" pattern - but that pattern is not defined It is defined at the end of 14.30.1, but it is (currently) a pattern that can?t be expressed, so is not in the grammar, but is a pattern that appears in the process of resolving a pattern (defined in 14.30.2). This is similar to the JLS treatment of intersection types, that don?t appear in the grammar, but are defined as types in Chapter 4. Thanks so much for these comments! Gavin -------------- next part -------------- An HTML attachment was scrubbed... URL: From gavin.bierman at oracle.com Wed Apr 20 15:51:20 2022 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Wed, 20 Apr 2022 15:51:20 +0000 Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available In-Reply-To: References: Message-ID: Thanks Aggelos, all corrected . Gavin On 14 Apr 2022, at 22:34, Angelos Bimpoudis > wrote: Hello Gavin, Exceptional work. Some minor proposals from my side... 14.11.1 includes a discussion about executable switch expressions and statements. It describes the transformation of default and case labels after resolution of patterns. Maybe this deserves a separate subsection. > A set of case elements is exhaustive for a type T if it contains a pattern that is unconditional at type T (14.30.3) I think this is a crucial rule since it brings the two worlds of totality and exhaustivity together. It deserves a tiny example here. Do you think it would be beneficial? > The set Q is exhaustive from component d at type V, where d is the component following c, V is the type of corresponding component field in T, and Q is the set of patterns containing every record pattern in P whose component pattern corresponding to c covers U. Instead of using the word "covers" we need to talk about the T <: U subtype relation. Covers is a remnant of the previous terminology set IIUC (which was good, but here is undefined). nitpicking: a instanceof expression -> an a enum -> an enum a unrefined -> an unrefined and that statement can completely normally -> can complete normally then it is further tranformed -> then it is further *transformed* or the RelationalExpression of a instanceof -> an Many thanks, Aggelos ________________________________ From: amber-spec-experts > on behalf of Gavin Bierman > Sent: 07 April 2022 13:40 To: amber-spec-experts > Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available Dear experts: The first draft of a spec covering both the third preview of Pattern Matching for switch (JEP number coming, but currently available at https://openjdk.java.net/jeps/8282272) and JEP 405 (Record Patterns) is now available at: http://cr.openjdk.java.net/~gbierman/PatternSwitchPlusRecordPatterns/PatternSwitchPlusRecordPatterns-20220407/specs/patterns-switch-jls.html Comments welcome! Thanks, Gavin -------------- next part -------------- An HTML attachment was scrubbed... URL: From gavin.bierman at oracle.com Wed Apr 20 15:51:24 2022 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Wed, 20 Apr 2022 15:51:24 +0000 Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available In-Reply-To: References: Message-ID: <35F0F7E6-29A7-4031-B0DA-68FA4B93173F@oracle.com> Thanks for spotting these, Guy. All corrected. Gavin On 10 Apr 2022, at 03:45, Guy Steele > wrote: Nice work defining new terminology for Chapter 14. Just a few nits: 14.11.1 A switch label is said to support a default if either it has a default label, or it has a case label with a default. Maybe: A switch label is said to support a default if either it has a default label, or it has a case label with a default case element. Any variable that is used but not declared in a whenexpression must either be final or effectively final (4.12.4). Make that: Any variable that is used but not declared in a whenexpression must be either final or effectively final (4.12.4). The switch block of a switch statement or a switch expression is switch compatible with the type of the selector expression, T, if all the switch labels in the switch block are switch compatible with T. I suggest: The switch block of a switch statement or a switch expression is switch compatible with the type of the selector expression, T, if every switch label in the switch block is switch compatible with T. because the following sentence defines the phrase ?a switch label is switch compatible with T ? rather than ?(a set of) switch labels are switch compatible with T ?. (These are basic principles of the style of writing I tried to establish for JLS: (a) once you define a specific form for a technical phrase, stick as closely as possible to that form; (2) where possible, focus on a single item in preference to sets of items?this means that ?every? is usually a more useful quantifier than ?all?.) [more to come? I?m too sleepy to continue reading right now] ?Guy -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Thu Apr 21 09:21:41 2022 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 21 Apr 2022 11:21:41 +0200 (CEST) Subject: case null / null pattern (v2) In-Reply-To: <5a13974d-c61b-a479-e1b3-4e582e7fb4cd@oracle.com> References: <1527944420.13948964.1650322198675.JavaMail.zimbra@u-pem.fr> <5a13974d-c61b-a479-e1b3-4e582e7fb4cd@oracle.com> Message-ID: <986972237.14986740.1650532901850.JavaMail.zimbra@u-pem.fr> > From: "Brian Goetz" > To: "Remi Forax" , "amber-spec-experts" > > Sent: Tuesday, April 19, 2022 10:02:22 PM > Subject: Re: case null / null pattern (v2) > With the currently specified semantics, the second pattern is dead, because > switches will only match null at the top level with a case null. This was an > accommodation to clarify that that the null-hostility of switch is a property > of switch, not patterns, and make it more clear when switch will NPE. "case Foo fooButNull" is equivalent to "case null" but with a binding typed as Foo that's why i ask if it should even compile, the compiler should ask for an explicit "case null". But because there is no null pattern, we can not do that if it appears as a sub-pattern. > Regardless, what you're asking for is a more precise remainder checking. The > first pattern matches all non-null Foo; because no case matches null, you're > asking that we recognize that there is a dominance relationship here. This is > reasonable to consider (though harder, because null makes everything harder.) I'm not sure it should be include in the dominance, because Foo(int) is a subtype of Foo, so case Foo foo -> case Foo(int _) foo -> should not compile. The ideal solution for me is to disable that pattern (record pattern + type pattern on the same type) and allow the null pattern, but as you said earlier, if we allow case(null), it's opening the pandora box because people will want to write case Foo(7). R?mi > On 4/18/2022 6:49 PM, Remi Forax wrote: >> I've found a way to encode the null pattern if you have a record >> record Foo(int x) { } >> Foo foo = ... >> return switch(foo) { >> case Foo(int _) foo -> "i'm a foo not null here !"; >> case Foo fooButNull -> "i can be only null here !"; >> }; >> I wonder if allowing those two patterns, a record pattern and a type pattern >> using the same type is a good idea or not, it seems a great way to obfuscate >> thing. >> R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Thu Apr 21 09:28:45 2022 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 21 Apr 2022 11:28:45 +0200 (CEST) Subject: Record pattern and side effects In-Reply-To: <2897C3E9-8DD2-49AE-9421-479753BB51A5@oracle.com> References: <448085588.13668630.1650188930595.JavaMail.zimbra@u-pem.fr> <2897C3E9-8DD2-49AE-9421-479753BB51A5@oracle.com> Message-ID: <1535408822.14991807.1650533325656.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "Brian Goetz" > To: "Remi Forax" > Cc: "amber-spec-experts" > Sent: Sunday, April 17, 2022 4:58:26 PM > Subject: Re: Record pattern and side effects > Yes, this is something we have to get ?on the record?. > > Record patterns are a special case of deconstruction patterns; in general, we > will invoke the deconstructor (which is some sort of imperative code) as part > of the match, which may have side-effects or throw exceptions. With records, > we go right to the accessors, but its the same game, so I?ll just say ?invoke > the deconstructor? to describe both. > > While we can do what we can to discourage side-effects in deconstructors, they > will happen. This raises all sorts of questions about what flexibility the > compiler has. > > Q: if we have > > case Foo(Bar(String s)): > case Foo(Bar(Integer i)): > > must we call the Foo and Bar deconstructors once, twice, or ?dealer?s choice?? > (I know you like the trick of factoring a common head, and this is a good > trick, but it doesn?t answer the general question.) > > Q: To illustrate the limitations of the ?common head? trick, if we have > > case Foo(P, Bar(String s)): > case Foo(Q, Bar(String s)): > > can we factor a common ?tail?, where we invoke Foo and Bar just once, and then > use P and Q against the first binding? > > Q: What about reordering? If we have disjoint patterns, can we reorder: > > case Foo(Bar x): > case TypeDisjointWithFoo t: > case Foo(Baz x): > > into > > case Foo(Bar x): > case Foo(Baz x): > case TypeDisjointWithFoo t: > > and then fold the head, so we only invoke the Foo dtor once? > > Most of the papers about efficient pattern dispatch are relatively little help > on this front, because the come with the assumption of purity / > side-effect-freedom. But it seems obvious that if we were trying to optimize > dispatch, our cost model would be something like arithmetic op << type test << > dtor invocation, and so we?d want to optimize for minimizing dtor invocations > where we can. yes ! > > We?ve already asked one of the questions on side effects (though not sure we > agreed on the answer): what if the dtor throws? The working story is that the > exception is wrapped in a MatchException. (I know you don?t like this, but > let?s not rehash the same arguments.) Wrapping exceptions into a MatchException destroy any idea of refactoring from a cascade of if ... instanceof to a switch. I think refactoring is a use case we should support. R?mi From brian.goetz at oracle.com Thu Apr 21 16:14:37 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 21 Apr 2022 12:14:37 -0400 Subject: [External] : Re: case null / null pattern (v2) In-Reply-To: <986972237.14986740.1650532901850.JavaMail.zimbra@u-pem.fr> References: <1527944420.13948964.1650322198675.JavaMail.zimbra@u-pem.fr> <5a13974d-c61b-a479-e1b3-4e582e7fb4cd@oracle.com> <986972237.14986740.1650532901850.JavaMail.zimbra@u-pem.fr> Message-ID: <45b6b758-2590-b91b-8713-ebebd087dff2@oracle.com> > "case Foo fooButNull" is equivalent to "case null" but with a binding > typed as Foo that's why i ask if it should even compile, > the compiler should ask for an explicit "case null". It may be "equivalent" in our eyes, but the language doesn't currently incorporate nullity into the type system.? So it is similar to other kinds of "equivalences", where the human knows more than the language, such as single-sealed classes: ??? sealed interface I permits A { } ??? final class A implements I { } ??? I i = ... ??? A a = i As humans, we know that the I must be an A, but we'd have to change the set of assignment (and other) conversions to reflect that.? We could, but right now, the language doesn't know this. I could construct other examples of things the programmer knows but the language doesn't, but I think you get my point -- if you want to raise this equivalence into the language, this is not merely about pattern dominance, this is an upgrade to the type system. > > I'm not sure it should be include in the dominance, because Foo(int) > is a subtype of Foo, so > ? case Foo foo -> > ? case Foo(int _) foo -> > should not compile. Dominance already handles this case.? A type pattern `X x` dominates a record pattern `X(...)`. -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Apr 21 16:20:24 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 21 Apr 2022 12:20:24 -0400 Subject: [External] : Re: Record pattern and side effects In-Reply-To: <1535408822.14991807.1650533325656.JavaMail.zimbra@u-pem.fr> References: <448085588.13668630.1650188930595.JavaMail.zimbra@u-pem.fr> <2897C3E9-8DD2-49AE-9421-479753BB51A5@oracle.com> <1535408822.14991807.1650533325656.JavaMail.zimbra@u-pem.fr> Message-ID: <399dc2fc-0179-4d7c-6568-10032c34d135@oracle.com> >> We?ve already asked one of the questions on side effects (though not sure we >> agreed on the answer): what if the dtor throws? The working story is that the >> exception is wrapped in a MatchException. (I know you don?t like this, but >> let?s not rehash the same arguments.) > Wrapping exceptions into a MatchException destroy any idea of refactoring from a cascade of if ... instanceof to a switch. > > I think refactoring is a use case we should support. Wrapping exceptions thrown from dtors does not affect refactoring. If I have: ??? if (x instanceof D(P)) A; ??? else if (x instanceof D(Q)) B; ??? else C; and I refactor to ??? switch (x) { ??????? case D(P): A; break; ??????? case D(Q): B; break; ??????? default: C ??? } Let's imagine that dtor D throws.? The wrapping happens when a dtor/accessor is invoked _implicitly_ as a result of evaluating a pattern match.? In both cases, we will wrap the thrown exception and throw MatchException.? In this way, both instanceof and switch are "clients of" pattern matching, and it is pattern matching that throws. I don't see any destruction here. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Thu Apr 21 23:48:44 2022 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Fri, 22 Apr 2022 01:48:44 +0200 (CEST) Subject: [External] : Re: Record pattern and side effects In-Reply-To: <399dc2fc-0179-4d7c-6568-10032c34d135@oracle.com> References: <448085588.13668630.1650188930595.JavaMail.zimbra@u-pem.fr> <2897C3E9-8DD2-49AE-9421-479753BB51A5@oracle.com> <1535408822.14991807.1650533325656.JavaMail.zimbra@u-pem.fr> <399dc2fc-0179-4d7c-6568-10032c34d135@oracle.com> Message-ID: <2092001046.15312550.1650584924891.JavaMail.zimbra@u-pem.fr> > From: "Brian Goetz" > To: "Remi Forax" > Cc: "amber-spec-experts" > Sent: Thursday, April 21, 2022 6:20:24 PM > Subject: Re: [External] : Re: Record pattern and side effects >>> We?ve already asked one of the questions on side effects (though not sure we >>> agreed on the answer): what if the dtor throws? The working story is that the >>> exception is wrapped in a MatchException. (I know you don?t like this, but >>> let?s not rehash the same arguments.) >> Wrapping exceptions into a MatchException destroy any idea of refactoring from a >> cascade of if ... instanceof to a switch. >> I think refactoring is a use case we should support. > Wrapping exceptions thrown from dtors does not affect refactoring. > If I have: > if (x instanceof D(P)) A; > else if (x instanceof D(Q)) B; > else C; > and I refactor to > switch (x) { > case D(P): A; break; > case D(Q): B; break; > default: C > } > Let's imagine that dtor D throws. The wrapping happens when a dtor/accessor is > invoked _implicitly_ as a result of evaluating a pattern match. In both cases, > we will wrap the thrown exception and throw MatchException. In this way, both > instanceof and switch are "clients of" pattern matching, and it is pattern > matching that throws. > I don't see any destruction here. I'm thinking about the refactoring from a code using accessors to a code using a deconstructor. By example, IDEs may propose to refactor this code if (x instanceof D d) A(d.p()); else B; to if (x instanceof D(P p)) A(p); else B; or vice versa If you wraps deconstructor exceptions, but not accessor exceptions you have mismatch. And as i said earlier, there is also the issue with a deconstructor calling another deconstructor (like your example with super), you may wrap a MatchException into a MatchException. R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Apr 22 13:34:29 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 22 Apr 2022 09:34:29 -0400 Subject: [External] : Re: Record pattern and side effects In-Reply-To: <2092001046.15312550.1650584924891.JavaMail.zimbra@u-pem.fr> References: <448085588.13668630.1650188930595.JavaMail.zimbra@u-pem.fr> <2897C3E9-8DD2-49AE-9421-479753BB51A5@oracle.com> <1535408822.14991807.1650533325656.JavaMail.zimbra@u-pem.fr> <399dc2fc-0179-4d7c-6568-10032c34d135@oracle.com> <2092001046.15312550.1650584924891.JavaMail.zimbra@u-pem.fr> Message-ID: > Let's imagine that dtor D throws.? The wrapping happens when a > dtor/accessor is invoked _implicitly_ as a result of evaluating a > pattern match.? In both cases, we will wrap the thrown exception > and throw MatchException.? In this way, both instanceof and switch > are "clients of" pattern matching, and it is pattern matching that > throws. > > I don't see any destruction here. > > > I'm thinking about the refactoring from a code using accessors to a > code using a deconstructor. > By example, IDEs may propose to refactor this code > > ? if (x instanceof D d) A(d.p()); else B; > > to > > ? if (x instanceof D(P p)) A(p); else B; > > or vice versa > If you wraps deconstructor exceptions, but not accessor exceptions you > have mismatch. OK, sure.? This bothers me zero.? Having an accessor (or dtor) throw is already really^3 weird; having a program depend on which specific exception it throws is really^32 weird.? (In both cases, they still throw an exception that you probably shouldn't be catching, with a clear stack trace explaining where it went wrong.)? Not a case to design the language around. Still not seeing any "destruction" here. -------------- next part -------------- An HTML attachment was scrubbed... URL: From gavin.bierman at oracle.com Tue Apr 26 14:34:49 2022 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Tue, 26 Apr 2022 14:34:49 +0000 Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available In-Reply-To: References: Message-ID: <8731CF9B-956D-4958-80EA-6BB34C392C6D@oracle.com> Dear experts: The latest draft of a joint spec covering JEP 427 (Third Preview of Pattern Matching for switch) and JEP 405 (Record Patterns) is available at: https://cr.openjdk.java.net/~gbierman/jep427+405/latest And, following convention, is also accessible from both of the following: https://cr.openjdk.java.net/~gbierman/jep405/latest https://cr.openjdk.java.net/~gbierman/jep427/latest Thanks, Gavin On 7 Apr 2022, at 12:40, Gavin Bierman wrote: Dear experts: The first draft of a spec covering both the third preview of Pattern Matching for switch (JEP number coming, but currently available at https://openjdk.java.net/jeps/8282272) and JEP 405 (Record Patterns) is now available at: http://cr.openjdk.java.net/~gbierman/PatternSwitchPlusRecordPatterns/PatternSwitchPlusRecordPatterns-20220407/specs/patterns-switch-jls.html Comments welcome! Thanks, Gavin -------------- next part -------------- An HTML attachment was scrubbed... URL: From maurizio.cimadamore at oracle.com Wed Apr 27 09:36:45 2022 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Wed, 27 Apr 2022 10:36:45 +0100 Subject: Draft Spec for Third Preview of Pattern Matching for Switch and Record Patterns (JEP 405) now available In-Reply-To: References: <8ef7b21f-1303-991d-89fa-c32f95cd3f1a@oracle.com> Message-ID: On 20/04/2022 16:44, Gavin Bierman wrote: > Thanks Maurizio! > >> On 8 Apr 2022, at 11:57, Maurizio Cimadamore >> wrote: >> >> Hi Gavin, >> great work - some comments: >> >> * in section 6 on names there's no mention of whether pattern >> variables are matched in the `when` clause. >> > This is covered in section 6.3.4? But maybe I misunderstand your point? Ah - missed this. > It is defined at the end of 14.30.1, > and this. Thanks for the clarifications! Maurizio -------------- next part -------------- An HTML attachment was scrubbed... URL: