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

David Alayachew davidalayachew at gmail.com
Mon Sep 25 20:52:18 UTC 2023


Annoying. Piper mail bungled my formatting. Resending, this time with
spaces instead of tabs. Yet another reason why spaces are superior to tabs.

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 4:50 PM David Alayachew <davidalayachew at gmail.com>
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/amber-dev/attachments/20230925/c154d8f5/attachment-0001.htm>


More information about the amber-dev mailing list