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

> On 9/11/2025 11:55 AM, Brian Goetz wrote:

>> I explicitly asked you to answer a question on the semantics when these
>> conversions were NOT involved before we moved onto these, because I think you
>> are conflating two separate things, and in order to get past just repeating
>> "but...lossy!", we have to separate them.

> I see Remi has lost interest in this discussion, so I will play both parts of
> the dialogue on his behalf now.

>> Let's start with the easy ones:

>> Object p = "foo"; // widen String to Object
>> Object q = Integer.valueOf(3) // widen Integer to Object
>> ...
>> if (p instanceof String s) { ... } // yes it is
>> if (q instanceof String s) { ... } // no it isn't

>> We can widen String and Integer to Object; we can safely narrow p back to
>> String, but we can't do so for q, because it is "outside the range" of
>> references to String (which embeds in "references to Object".) Does this make
>> sense so far?

> Remi: It has always been this way.

>> OK, now let's do int and long.

>> long small = 3
>> long big = Long.MAX_VALUE

>> if (small instanceof int a) { ... } // yes, it is
>> if (big instanceof int b) { ... } // no, it isn't

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

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

You can write: 
short s = 42; 
switch(s) { 
case char c -> IO.println("char"); 
case short s -> IO.println("short"); 
} 

So when you have a switch with both a char and a short, you have no "right" way 
to write it. 
BTW, why those examples compile is also an interesting question and how can i 
pattern match on an unsigned int is another good one. 

So, yes, it's the semantics of primitive types in it's full glory, but that's a 
problem because most developers does not rely on the semantics of primitive 
types in their day to day job, 
That the reason why you can write so many puzzlers, because the primitive types 
semantics is puzzling if the JLS is not your bible. 

That why i said that this will be hard to teach, because for my students the 
primitive type relationship are arcane. 
In a way, you and me are from a different generation when saving bits was 
something important, this is not anymore, JSON does not make a different 
between int and short. 

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. 

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

As a developer, i think i would prefer to be able to write ArrayList<int> than 
being able to pattern match on primitives with a semantics that only works for 
primitives. 

To summarize, using only primitive types relationship as a semantics for 
pattern matching on primitive types is not a good idea because Java developers 
do not care about primitive conversions and it inhibit us to provide more 
important features trying to heal the rift between objects and primitives. 
regards, 
Rémi 

Reply via email to