Primitives in instanceof and patterns

Brian Goetz brian.goetz at oracle.com
Tue Sep 13 18:14:52 UTC 2022


I'm going to try and address these points *for the benefit of everyone 
else*.  (Note to Remi only: this is not an invitation to continue the 
back and forth, as doing so would likely be unconstructive unless you 
have something either (a) radically new that no one has thought of yet 
and/or (b) something that is so obviously right and compelling that I 
will immediately weep with embarrassment for how wrong I was.  That's 
the bar at this point.  I get that you hate this feature.  You've made 
that manifestly clear.  But unless you have some significantly new light 
to shed on it, it is unconstructive to just keep banging this drum, and 
you are creating an environment where others feel less comfortable 
sharing their thoughts, which is unacceptable.)

> 1) having a primitive pattern doing a range check is useless because 
> this is rare that you want to do a range check + cast in real life,
>    How many people have written a code like this
>
>     int i = ...
>     if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) {
>       byte b = (byte) i;
>       ...
>     }
>
>    It's useful when you write a bytecode generator without using an 
> existing library, ok, but how many write a bytecode generator ?
>    It should not be the default behavior for the primitive type pattern.

This argument stems from a misunderstanding of what we are trying to 
accomplish here.  Yes, it is correct that `case byte b` is not something 
everyone will use (I have written this many times, though I admit this 
is probably unusual.)  But that's not the point of this exercise; the 
point of the exercise is uniformity, in part because the lack of 
uniformity is complexity, and in part we want to offer new semantic 
symmetries that programmers can count on.  You are trying to tinker at 
the margins, asking if each conversion carries its weight; that's a 
recipe for creating new, ad-hoc complexity surface.  Sometimes that's 
the right move, and sometimes it is unavoidable, but there is such an 
obviously correct interpretation of primitive instanceof here -- "would 
a cast to this type be safe" -- that it would be an unforced error to 
opt for the ad-hoc complexity just because you can't imagine using it 
that often.

If I have a record:

     record R(int x) { }

I can construct it with

     new R(aShort)

but under the strict semantics of primitive type patterns,  I cannot 
deconstruct it with

     case R(short s) { }

which would ask: "could this record have come from a constructor 
invocation `new R(s)`".   And this is gratuitously different than the 
correspond case with reference widening:

     record S(Object o) { }

     S s = new S("foo");
     if (s instanceof S(String ss)) { ... }

Further, I take objection to your continued characterization of this as 
a "range check", as this is a mischaracterization as well as minimizing 
what is going on.  Casting subsumes boxing and unboxing as well as 
widening and narrowing, so a more correct characterization would be 
"could I cast this without loss or error to a short".  Which applies not 
only to wider and narrower types, but to types like Short and Object.  
Just like `instanceof` for reference types, which asks whether the type 
could be cast to another type.  And without creating a new context for 
what is allowable.

Not only is the term "useless" unconstructive, but it is not even the 
right measure.  The bar here is not "would people use it a lot."  We're 
making the language simpler by making it more uniform. To say "let's 
gratuitously knock some of the boxes out of the cast matrix because I 
can't imagine using them" only makes the language more complicated.

> 2) It's also useless because there is no need to have it as a pattern, 
> when you can use a cast in the following expression
>     Person person = ...
>     switch(person) {
>       // instead of
>       // case Person(double age) -> foo(age);
>       // one can write
>       case Person(int age) -> foo(age);  // widening cast
>     }

Same argument (also you got your example backwards).  I get that you 
think its fine to have to do this, but it is yet another gratuitous 
asymmetry between aggregation and destructuring that confuses people 
about how destructuring works.  Why can you pass an int or a double to 
`new Person`, but could only take an `double` out?  Whereas with 
Object/String, you could take either out?

Again, this is gratuitous complexity, which I think is rooted in your 
unwillingness to let go of "instanceof means subtype."  Sorry, it 
doesn't any more (but it means something that generalizes it.)

> 3) when you read a conditional primitive patterns, you have no idea 
> what is the underlying operation until you go to the declaration 
> (unlike the code just above).

This is the same complaint you had in the past about partial and total 
nested patterns.  As I've said, I understand why you find it 
uncomfortable ("action at a distance"), but we evaluated the pros and 
cons extensively already, and we made our decision.  There's no reason 
to reopen it here, nor are the considerations any different in this case.

> 4) if we change the type pattern to be not just about subtyping, we 
> should revisit the JLS to avoid to have too many different semantics.

This is FUD, implying that we are going to have to reexamine 
everything.  I don't buy it.  Many of the things that lean on subtyping 
today are just ... subtyping.  And the things that have conversions 
involving primitives already lean on conversions and contexts.

By way of concrete example, you raised the question about covariant 
overrides.  Which was a good example, and which I appreciate, but I wish 
you would have raised it differently.

A constructive way to raise this would be: "Do we also want to reexamine 
covariant overrides to use castability (or some other criteria) rather 
than subtyping?"

An unconstructive way to raise this would be: "This feature is bad, look 
at the problems you are creating for covariant overrides, everything 
will have to be reexamined."

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-spec-observers/attachments/20220913/c0174124/attachment-0001.htm>


More information about the amber-spec-observers mailing list