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."

Reply via email to