sent to soon, please discard the previous mail, a cat was involved :) 

> From: "Brian Goetz" < brian.go...@oracle.com >
> To: "Remi Forax" < fo...@univ-mlv.fr >
> Cc: "amber-spec-experts" < amber-spec-expe...@openjdk.java.net >
> Sent: Wednesday, September 17 , 2025 2:12:09 PM
> Subject: Re: Type primitive pattern: the problem with lossy conversions

>>>> What these questions are asking is: can I safely narrow these longs to 
>>>> int, just
>>>> like the above. In the first case, I can -- just like with String and 
>>>> Object.
>>>> In the second, I can't -- just like with Integer and Object. Do we agree 
>>>> these
>>>> are the same?

>>> Remi: Yes Socrates, I believe they are.
>> No, they are not.
>> When you widen an Integer to an Object, it stays an Integer at runtime, when 
>> you
>> widen an int to a long, you transform it to a long, the original int is lost.

> Lost? Numbers can be lost? OMG, that's terrible! Can you imagine if we lost a
> _really_ important number, like one or zero? Society would collapse. Something
> must be done!
Okay, wrong wording, what i wanted to say is the original fact that the value 
was an int is lost. 
Not the value itself, my bad on that. 

> Oh, wait, that's not how it works. Numbers, being platonic ideals, just ARE. I
> can summon zero into being at will:

> int a = 0;

> Can I summon multiple zeroes? Let me try:

> int a = 0;
> int b = 0;

> No, I can't, they are the same zero. I cannot discern any difference between 
> the
> zero in a and the zero in b (this is the "substitutibility" criteria on which
> Valhalla equality is based.)

> If I put zero in an int, and then move it to a long, and back, can I tell the
> difference?

> int a = 0;
> long b = a;
> int c = (int) b;

> No, I cannot; the zero in a and the zero in c are the same value. And --
> crucially -- the zero in *b* is not a "different zero". This concept has been
> embedded in the JLS forever. JLS 4.2 defines byte, short, int, and long as
> binary encodings of _integers_.
yes, 
what i wanted to say is that the type is lost,. 

In the substitutibility test of Valhalla, the type is check first so there is 
no issue, 

> Did we *have* to do this right now? No, we didn't; we could have continued to
> allow pattern matching on primitives to be invariant, at least for a while. 
> But
> that would be increasingly problematic as Valhalla adds new numeric types; 
> we'd
> be able to convert them via assignment, but not ask if they are the same.
> Similarly, it would make constant patterns more difficult.
There is another alternative to the pattern matching on primitives being 
invariant, 
o instanceof Foo foo can be o.getClass() == Foo.class && o == foo. 

It works for constant, it works for boxing, it just does not work for primitive 
conversions or type witness conversions, 
those can be implemented with deconstructors / witness deconstructors ?? 

> Your puzzlers -- like the objection about lossy conversions -- are not about
> pattern matching, they are merely that pattern matching reveals existing
> decisions about the language that we might not, in hindsight, like.

>> Here are examples (puzzlers) exposing the rift.

>> char letter = 'A';
>> switch(letter) {
>> case short s -> IO.println("short");
>> case char c -> IO.println("char");
>> }

>> Here you have a character, typed as a char but it prints "short".

> Your objection here is that that `char` is not really a character type, it is 
> a
> 16 bit signed integral type, with support for literals that look like
> characters. But Java will freely convert between characters and other integral
> types; this is your objection, and pattern matching merely makes it more
> obvious. Same with your other example.

>> Moreover, with Valhalla, we want to try to make primitive to looks more like
>> value class, we talk about by example allowing ArrayList<int> as an example,
>> but this goal is in conflict with the semantics of the primitive type pattern
>> JEP, because the JEP semantics is based on a world where primitives and 
>> objects
>> do not talk to each others.

> A better way to think about this is that this JEP is preparing the way for
> pattern matching on values. When Valhalla is complete, the world of references
> and primitives will give way to one of identity objects and value objects.
> Identity objects get their polymorphism through inheritance (which is
> transitive); value objects get their polymorphism through point-to-point
> conversions. This JEP takes the first step by providing a unified framework 
> for
> type patterns, based on the set of conversions permitted in cast context (the
> most permissive set of conversions), and the notion of _exact conversion_ (was
> the conversion lossy or not.) This sets the stage for pattern matching in
> Valhalla.
Only the ""numeric"" value objects get their polymorphism through 
point-to-point conversions using a witness, 
so why not putting the deconstructors in the witness too ? 

>> Here is another example:
>> record Box<T>(T value) {}
>> int value = 42;
>> switch(new Box<>(value)) {
>> case byte b -> IO.println(b);
>> case int i -> IO.println(i);
>> }

>> This one does no compile because the int value is automatically 
>> auto-promoted to
>> an Integer, which follows different rules than the primitive rules (per this
>> JEP).

> I'm not sure what your objection is, but like the other examples, I am sure it
> has nothing to do with pattern matching. The semantics of switch are defined
> relative to the selector expression, the value and type of which has nothing 
> to
> do with the patterns in the body of the switch. `new Box<>(value)` is an
> expression with its own type; we can then match on it with patterns that are
> applicable to that type. Whether or not we can express primitive type patterns
> doesn't affect the typing of the selector.
It's an issue when you start to adds '!', by compatibility, you wanted to work 
like an Integer in term of dynamic type checking but as an int in term of 
binding conversion, so it's backward compatible and at the same time you get 
Integer! to be superficially equivalent to int. The illusion breaks when you 
can narrow int to byte. 

> Your claim that this "inhibits" the unification of objects and primitives is a
> very big one. Go take as much time as you need to capture this argument 
> clearly
> and persuasively.
Now i claim that: 
- apart if you know the JLS very well, the proposed semantics is confusing for 
most people, 
- that there is another semantics that may work better based on the getClass + 
substituability test (but i'm not sure) 
- we do not know how to correctly add '!' and witness conversions 

So we should at least wait before trying to include this JEP in the JDK. 

regards, 
Rémi 

Reply via email to