New candidate JEP: 455: Primitive types in Patterns, instanceof, and switch (Preview)

David Alayachew davidalayachew at gmail.com
Tue Sep 26 03:50:11 UTC 2023


Hello Brian,

Thank you for your response!

> Imagine it worked the way your intuition tells you it
> should work. I'll start with the old case, nested record
> patterns. Now, suppose you have:
>
>     record Pair<T,U>(T t, U u) { }
>
> ...
>
>     Pair<Pair<A, B>, Pair<C, D>> p = ...
>
>     switch (p) {
>         case Pair(Pair(A a, B b), Pair(C c, D d)): ...
>     }
>
> Under the David rules, this switch is not exhaustive,
> because it matches neither Pair(null, _) or
> Pair(_, null). So what cases would you have to add to the
> switch to satisfy the compiler's complaints of
> non-exhaustiveness?  Write the code, and then tell me if
> you want to program in that language...

Excellent example lol. That clarifies it perfectly. So, the reason why we
do not want to have this level of specificity required by the programmer is
because it would cascade into a giant pile of writing down edge cases.
Makes perfect sense.

> The first part of the example (Box<Box<String>>) is not
> new to this JEP; this is how nested patterns work. (If
> you think about it for long enough, you realize that the
> alternatives are all nearly intolerable.) The
> Box<Integer> example just adds one more form of
> remainder.

I always try and think these questions through before asking them, but
there's only so far my brain can stretch without trying things out myself.
Thank you very much.

> The gap between exhaustiveness and totality (which we
> call "remainder") is indeed somewhat counterintuitive at
> first, because we would like for these two words to mean
> the same thing. There were many, many discussions
> regarding this in Record Patterns. This JEP extends the
> surprise a little bit, but only in a quantitative, not
> qualitative way; unboxing conversions are another place
> where we encounter a gap between these two similar
> concepts.

That word "Remainder" reminds me now. It looks like my exact question was
already answered in the "Remainder" doc from a few months ago.

https://openjdk.org/projects/amber/design-notes/patterns/exhaustiveness#nested-patterns-and-sealed-types

In my defense though, that doc was pretty content-dense, full of explaining
how the unintuitive is better than the alternative.

I guess another take away from this exchange is recognizing that what I
have been looking at this entire time is called the "Remainder". Knowing
that gives me a cue to start watching for nulls, as well as other possible
remainders.

Thank you for your time and help!
David Alayachew

On Mon, Sep 25, 2023 at 8:07 PM Brian Goetz <brian.goetz at oracle.com> wrote:

> The gap between exhaustiveness and totality (which we call "remainder") is
> indeed somewhat counterintuitive at first, because we would like for these
> two words to mean the same thing.  There were many, many discussions
> regarding this in Record Patterns.  This JEP extends the surprise a little
> bit, but only in a quantitative, not qualitative way; unboxing conversions
> are another place where we encounter a gap between these two similar
> concepts.
>
> The first part of the example (Box<Box<String>>) is not new to this JEP;
> this is how nested patterns work.  (If you think about it for long enough,
> you realize that the alternatives are all nearly intolerable.)  The
> Box<Integer> example just adds one more form of remainder.
>
> Imagine it worked the way your intuition tells you it should work.  I'll
> start with the old case, nested record patterns.  Now, suppose you have:
>
>     record Pair<T,U>(T t, U u) { }
>
> ...
>
>     Pair<Pair<A, B>, Pair<C, D>> p = ...
>
>     switch (p) {
>         case Pair(Pair(A a, B b), Pair(C c, D d)): ...
>     }
>
> Under the David rules, this switch is not exhaustive, because it matches
> neither Pair(null, _) or Pair(_, null).  So what cases would you have to
> add to the switch to satisfy the compiler's complaints of
> non-exhaustiveness?  Write the code, and then tell me if you want to
> program in that language...
>
>
>
>
>
> On 9/25/2023 4:50 PM, David Alayachew wrote:
>
> Hello Mark,
>
> Thank you for posting this! I am shocked to see the Level of Effort set to
> M -- all those sharp edges makes this seem much more complex!
>
> I was very surprised to see the following snippet in the JEP.
>
>
> > With pattern labels involving record patterns, some
> > patterns are considered to be exhaustive even when they
> > are not unconditional. For example:
> >
> > Box<Box<String>> bbs = ...
> > switch (bbs) {
> >     case Box(Box(String s)): ...
> > }
> >
> > This switch is considered exhaustive on Box<Box<String>>
> > even though the pattern Box(Box(String s)) will not match
> > the pathological value new Box(null), which is in the
> > remainder set and is handled by a synthetic default
> > clause that throws MatchException.
> >
> > With the introduction of primitive type patterns, we
> > observe that unboxing follows the same philosophy. For
> > example:
> >
> > Box<Integer> bi = ...
> > switch (bi) {
> >     case Box(int i): ...
> > }
> >
> > This switch is considered exhaustive on Box<Integer> even
> > though the pattern Box(int i) will not match the
> > pathological value new Box(null), which is in the
> > remainder set.
>
> This surprises me because it feels like the analogy of Box<Box<String>> is
> a poor comparison to this. More specifically, it feels like comparing
> Apples and Oranges.
>
> The reason I feel that way is because I have a mental model in my head
> that has the following rules.
>
> * If I have a top level pattern, it does not match null. That's what case
> null is for.
>
>     * For example, case String s, I do not expect s to be null.
>
>    * Because of this, I understand why a case int i would be considered
> exhaustive, it seems to follow the same rules as a type pattern.
>
> * And more specifically, the rules of exhaustiveness seem to align here
> too. If instead of case String s, I had case Foo f, I would assume that the
> pattern is exhaustive if (f) can match the full domain, not including null.
>
> * If I have a nested pattern, that pattern can match null.
>
>     * For example, case Box(String s), I can expect s to maybe be null.
>
> * Because of this, I do not understand why a case int i would be
> considered exhaustive, because it seems to break from the rules of a type
> pattern thus far.
>
> * To give an example, if I have record Bar(int i), and then I later
> refactor that to be record Bar(Integer i), I would hope that my type
> pattern would no longer be exhaustive. But it sounds a lot like the above
> passage implies it WOULD be exhaustive.
>
> I do not understand this decision. Could you help me understand the what
> and the why? I also want to know your response to the sharp corner I raised
> when it comes to refactoring primitives to and from their boxed variants.
> Since Valhalla is on its way (hopefully bringing with it the ability to
> opt-in and opt-out of nullability), it feels like this sharp corner is
> going to protrude even further and be even sharper. Could you address that
> concern too please?
>
> Thank you for your time and help!
> David Alayachew
>
> On Mon, Sep 25, 2023 at 10:09 AM Mark Reinhold <mark.reinhold at oracle.com>
> wrote:
>
>> https://openjdk.org/jeps/455
>>
>>   Summary: Enhance pattern matching by allowing primitive type patterns
>>   to be used in all pattern contexts, align the semantics of primitive
>>   type patterns with that of instanceof, and extend switch to allow
>>   primitive constants as case labels. This is a preview language feature.
>>
>> - Mark
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/jdk-dev/attachments/20230925/92421c35/attachment-0001.htm>


More information about the jdk-dev mailing list