From forax at univ-mlv.fr Wed Sep 10 09:39:25 2025 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 10 Sep 2025 11:39:25 +0200 (CEST) Subject: Primitive type pattern (as actually specified) is considered harmful Message-ID: <2137548601.26083017.1757497165272.JavaMail.zimbra@univ-eiffel.fr> Hello all, The idea of JEP 507 is for the following code: Object o = "foo"; switch(o) { case String s -> ... default -> ... } The "case String" recovers the dynamic class of the value. The assignment does a widening and the pattern matching does the narrowing back to the original class. This can be seen as the transformation chain, String -> Object -> String. The JEP 507 proposes to apply the same principle to primitive types, By example, the transformation byte -> int -> byte, the pattern matching acting as a kind of inverse operation. For me, while this idea is coherent with itself, it fails at different levels. # Primitive conversions can be lossy With primitive types, the widening can be lossy (int to float is lossy, long to double is lossy), and from a mathematical point of view inverting a lossy function makes no sense. This give us this kind of puzzler: record Plane(float x, float y) {} void main() { Plane plane = new Plane(200_000_007, 16_777_219); switch (plane) { case Plane(int x, int y) -> IO.println("plane " + x + " " + y); default -> IO.println("not a plane"); } } You may say that the bug lies in the fact that Java should not allow lossy conversions, And I would agree, but it does not change the fact that conceptually, the pattern matching is trying to invert a lossy function, which again make no sense. # This is the wrong semantics For most people, int is equivalent to Integer!, this is also where we are aiming for Valhalla. Given that a switch can match null but only using a separately case null, matching a primitive type or its corresponding wrapper types should be equivalent. Sadly, this is not the semantics defined by the JEP 507. I propose, instead of the semantics of the JEP 507, to use two rules: - If the value switched upon is an Object, a "case int" should be equivalent to a "case Integer" and vice versa. By example: Object o = ... switch(o) { case int i -> ... default -> ... } should be equivalent to Object o = ... switch(o) { case Integer i -> ... default -> ... } - If the value switched upon is a primitive type, then only conversion that can occur is a boxing conversion. int v = ... switch(v) { case Integer _ -> ... // ok } If people want to know if an int can be safely converted to a byte, I think that using a static deconstructor method is better. regards, R?mi From brian.goetz at oracle.com Wed Sep 10 13:03:43 2025 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 10 Sep 2025 09:03:43 -0400 Subject: Primitive type pattern (as actually specified) is considered harmful In-Reply-To: <2137548601.26083017.1757497165272.JavaMail.zimbra@univ-eiffel.fr> References: <2137548601.26083017.1757497165272.JavaMail.zimbra@univ-eiffel.fr> Message-ID: <9554891d-e2b5-4ada-83e5-da07395be702@oracle.com> > considered harmful We can add that to the list of phrases that Remi is not allowed to utter on this list. > # This is the wrong semantics I believe we've discussed several time how this is the "wrong" way to make your case. But, presentation aside, I can't really figure out which argument you are actually trying to make. What I see, though, in your objections, is a classic fallacious pattern: ?- language already has features X_i (for i in 0..N, N is large) ?- language adds feature Y, which is orthogonal to all X_i ?- the combination of Y and X_4302 is "confusing" ?- Y gets blamed ?- the inevitable bargaining begins, where it is attempted to make Y 1000x more complicated to avoid this particular interaction One reason that this is an unhelpful way to go about things is that it is optimizing for the wrong thing.? The extension of instanceof to primitive types is utterly simple and straightfoward, and consistent with its meaning for reference types; the simplicity of this is a source of strength. To be clear, here's what's going on: ?- Today, for reference types, `x instanceof T` asks "can the reference x be safely cast to T", where "safe" means "not null, and conversion without CCE". ?- A language that has casts needs instanceof (or the equivalent), since you should be able to ask "would it succeed" before doing something that might fail. ?- We extend the interpretation `x instanceof T` to _all_ types, appealing to the same rule: would casting be safe.? We extend the interpretation of "not safe" as "losing data" in addition to errors. ?- Type patterns for primitives are built on instanceof in the same way that they are for reference types. That's it; that's the whole feature.? But, because it is built on casting, to the degree that you might be confused by casting, you will be confused (in exactly the same way) by instanceof. The corner cases around nullity are mostly a distraction; Valhalla will refine these in a way that gives you more control (e.g., `instanceof T?` vs `instanceof T!`) and makes it more clear what is going on with respect to nullity.? We should set these aside for now. The details of conversion between integral and floating point types is already problematic; we know this.? Specifically, we allow certain conversions between int/long and float/double _in assignment and method context_ when we should not; this is sadly an example of copying too literally from the C spec in 1995.? We can talk about that (but, not in the context of an "X is wrong" discussion), and there are things we can do to improve things. In your example, the problematic part is really: ? ? Plane plane = new Plane(200_000_007, 16_777_219); because this is a lossy implicit conversion.? And it would be totally reasonable to warn here (in fact, we would like to do so.)? But don't blame the pattern match for this.? That's complaining of is "conversion between int and float is confusing, and now you gave me a feature that lets me do more conversion, so is is more confusing!"? That's fine, but you have to stop before you leap madly to "so its wrong." I suggest that, instead of replying to individual points, you start over, and think carefully about what you are actually trying to say.? Maybe you have an argument here, but all I see is a mess of "I found a confusing example, so its all wrong."? Best to start over. On 9/10/2025 5:39 AM, Remi Forax wrote: > Hello all, > The idea of JEP 507 is for the following code: > > Object o = "foo"; > switch(o) { > case String s -> ... > default -> ... > } > > The "case String" recovers the dynamic class of the value. > The assignment does a widening and the pattern matching does the narrowing back to the original class. > This can be seen as the transformation chain, String -> Object -> String. > > The JEP 507 proposes to apply the same principle to primitive types, > By example, the transformation byte -> int -> byte, the pattern matching acting as a kind of inverse operation. > > For me, while this idea is coherent with itself, it fails at different levels. > > > # Primitive conversions can be lossy > > With primitive types, the widening can be lossy (int to float is lossy, long to double is lossy), > and from a mathematical point of view inverting a lossy function makes no sense. > > This give us this kind of puzzler: > > record Plane(float x, float y) {} > > void main() { > Plane plane = new Plane(200_000_007, 16_777_219); > > switch (plane) { > case Plane(int x, int y) -> IO.println("plane " + x + " " + y); > default -> IO.println("not a plane"); > } > } > > You may say that the bug lies in the fact that Java should not allow lossy conversions, > And I would agree, but it does not change the fact that conceptually, the pattern matching is trying to invert a lossy function, which again make no sense. > > > # This is the wrong semantics > > For most people, int is equivalent to Integer!, this is also where we are aiming for Valhalla. > Given that a switch can match null but only using a separately case null, matching a primitive type or its corresponding wrapper types should be equivalent. > > Sadly, this is not the semantics defined by the JEP 507. > > I propose, instead of the semantics of the JEP 507, to use two rules: > - If the value switched upon is an Object, a "case int" should be equivalent to a "case Integer" and vice versa. > > By example: > > Object o = ... > switch(o) { > case int i -> ... > default -> ... > } > > should be equivalent to > > Object o = ... > switch(o) { > case Integer i -> ... > default -> ... > } > > > - If the value switched upon is a primitive type, then only conversion that can occur is a boxing conversion. > > int v = ... > switch(v) { > case Integer _ -> ... // ok > } > > If people want to know if an int can be safely converted to a byte, I think that using a static deconstructor method is better. > > regards, > R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Wed Sep 10 14:31:00 2025 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 10 Sep 2025 16:31:00 +0200 (CEST) Subject: Primitive type pattern (as actually specified) is considered harmful In-Reply-To: <9554891d-e2b5-4ada-83e5-da07395be702@oracle.com> References: <2137548601.26083017.1757497165272.JavaMail.zimbra@univ-eiffel.fr> <9554891d-e2b5-4ada-83e5-da07395be702@oracle.com> Message-ID: <1310354903.26360960.1757514660614.JavaMail.zimbra@univ-eiffel.fr> > From: "Brian Goetz" > To: "Remi Forax" , "amber-spec-experts" > > Sent: Wednesday, September 10, 2025 3:03:43 PM > Subject: Re: Primitive type pattern (as actually specified) is considered > harmful > > considered harmful > We can add that to the list of phrases that Remi is not allowed to utter on this > list. >> # This is the wrong semantics > I believe we've discussed several time how this is the "wrong" way to make your > case. But, presentation aside, I can't really figure out which argument you are > actually trying to make. > What I see, though, in your objections, is a classic fallacious pattern: > - language already has features X_i (for i in 0..N, N is large) > - language adds feature Y, which is orthogonal to all X_i > - the combination of Y and X_4302 is "confusing" > - Y gets blamed > - the inevitable bargaining begins, where it is attempted to make Y 1000x more > complicated to avoid this particular interaction > One reason that this is an unhelpful way to go about things is that it is > optimizing for the wrong thing. The extension of instanceof to primitive types > is utterly simple and straightfoward, and consistent with its meaning for > reference types; the simplicity of this is a source of strength. I agree, i disagree about the proposed semantics, not about the idea to extend instanceof/switch to primitives. > To be clear, here's what's going on: > - Today, for reference types, `x instanceof T` asks "can the reference x be > safely cast to T", where "safe" means "not null, and conversion without CCE". > - A language that has casts needs instanceof (or the equivalent), since you > should be able to ask "would it succeed" before doing something that might > fail. > - We extend the interpretation `x instanceof T` to _all_ types, appealing to the > same rule: would casting be safe. We extend the interpretation of "not safe" as > "losing data" in addition to errors. > - Type patterns for primitives are built on instanceof in the same way that they > are for reference types. > That's it; that's the whole feature. But, because it is built on casting, to the > degree that you might be confused by casting, you will be confused (in exactly > the same way) by instanceof. The idea to uses the same syntax "cast" for different things comes from C/C++, C++ was later changed to recognize that it was a mistake to use the same syntax for different semantics. So different kinds of casts (primitive, object) have different meanings for what "safe" means. An object cast can throw a runtime exception, a primitive cast can not throw but does a lossy conversion instead. As you said, the idea of this JEP is to extend the interpretation of what safe/not safe means, but your interpretation is not the only possible interpretation. A more reasonable interpretation, is that "safe" means it's that you get the ""same"" original value (same being defined as: getClass (or subclass) and ==). - with Object o = "foo", o instanceof String s is safe because o.getClass() == String and o == s - with Object o = new Integer(3), o instanceof int i is safe because o.getClass() == Integer and o == i. Your interpretation has several issues: - you define "safe" as the revert operation on something which can be lossy (my brain can not compute that), - different values of the same class can be converted into different types, - it does not work well with Valhalla, especially with the phase 3 (i.e. let's pretend that a primitive type is a value type) * - your interpretation is surprising when i've tried to explain it to others people (even to people coming to JUGs). regards, R?mi * record Point(Integer! x, Integer! y) {} and record Point(int x, int y) does not behave the same way with the semantics you have defined. -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Wed Sep 10 15:02:46 2025 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 10 Sep 2025 11:02:46 -0400 Subject: Primitive type pattern (as actually specified) is considered harmful In-Reply-To: <1310354903.26360960.1757514660614.JavaMail.zimbra@univ-eiffel.fr> References: <2137548601.26083017.1757497165272.JavaMail.zimbra@univ-eiffel.fr> <9554891d-e2b5-4ada-83e5-da07395be702@oracle.com> <1310354903.26360960.1757514660614.JavaMail.zimbra@univ-eiffel.fr> Message-ID: <36774ec0-fde4-49f5-937c-76e9c73d9c76@oracle.com> > - you define "safe" as the revert operation on something which can be > lossy (my brain can not compute that), Sorry about your brain, but I think this is straightforward.? Given sets A and B, with a pair of functions ? ? f: A -> B ? ? g: B -> A we can discuss properties of the composite functions `f . g` and `g . f`, and there are useful algebraic structures we can appeal to (isomorphisms, embedding-projection pairs, etc) that can characterize their behavior.? For example, A and B are isomorphic if `f . g == id_B` and `g . f == id_A`. Type pairs like A=int and B=long form _embedding projection pairs_, where `g . f` is an identity function on A, and `f . g` is a well-behaved _approximation_ of the identity function on B (there's a more rigorous definition, but let's just call it "potentially lossy in a stable way", so that repeated application of f and g converges to an identity after the first loss.)? With the exception of the "weird" widening conversions that we borrowed too liberally from C, which are called "widening" but aren't, conversions pairs that are described as widening/narrowing pairs in JLS 5 have this property: int/long, int/double, float/double, String/Object, etc. But it is really simple: c instanceof P iff the value of c is exactly representable in type P. It might be tempting to say "if I can cast and cast back and get the same answer", but unfortunately that doesn't work, because there are lossy conversions whose inverse is also lossy in an exactly compensating way.? _This_ would be confusing. > - it does not work well with Valhalla, especially with the phase 3 > (i.e. let's pretend that a primitive type is a value type) * As I said, I think this is a distraction, but if you disagree, you are going to need to provide a much more exhaustive description of how you think Valhalla will work and why this is a conflict, than just appealing to claims like "won't work with Valhalla."? ?Or, alternately, if you want to focus entirely on that, then that's fine, start a new thread (but I would expect that mail to have more "how would X work" questions rather than assertions about how it would work.) > - your interpretation is surprising when i've tried to explain it to > others people (even to people coming to JUGs). I believe that, but as an educator, you should well know that often "surprise" is not an indication of "wrong theory" but "wrong user mental model", which implies that what is needed is more education. If we consistently designed the language for least-surprise, we would very quickly find ourselves in a pit where we cannot add anything, both because something always surprises a 1995-era programmer, and because we would have added layers and layers of ad-hoc "what we think the user expects" features that we would eventually collapse under our own weight.? (The "implicit conversion from float to int" is an example of this kind of mistake, and look where that got us.) But I still don't see anything concrete enough here to engage with. So again, please: start again. From forax at univ-mlv.fr Thu Sep 11 10:45:01 2025 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 11 Sep 2025 12:45:01 +0200 (CEST) Subject: Primitive type pattern (as actually specified) is considered harmful In-Reply-To: <36774ec0-fde4-49f5-937c-76e9c73d9c76@oracle.com> References: <2137548601.26083017.1757497165272.JavaMail.zimbra@univ-eiffel.fr> <9554891d-e2b5-4ada-83e5-da07395be702@oracle.com> <1310354903.26360960.1757514660614.JavaMail.zimbra@univ-eiffel.fr> <36774ec0-fde4-49f5-937c-76e9c73d9c76@oracle.com> Message-ID: <2042548064.26921634.1757587501317.JavaMail.zimbra@univ-eiffel.fr> ----- Original Message ----- > From: "Brian Goetz" > To: "Remi Forax" > Cc: "amber-spec-experts" > Sent: Wednesday, September 10, 2025 5:02:46 PM > Subject: Re: Primitive type pattern (as actually specified) is considered harmful >> - you define "safe" as the revert operation on something which can be >> lossy (my brain can not compute that), > > Sorry about your brain, but I think this is straightforward.? Given sets > A and B, with a pair of functions > > ? ? f: A -> B > ? ? g: B -> A > > we can discuss properties of the composite functions `f . g` and `g . > f`, and there are useful algebraic structures we can appeal to > (isomorphisms, embedding-projection pairs, etc) that can characterize > their behavior.? For example, A and B are isomorphic if `f . g == id_B` > and `g . f == id_A`. > > Type pairs like A=int and B=long form _embedding projection pairs_, > where `g . f` is an identity function on A, and `f . g` is a > well-behaved _approximation_ of the identity function on B (there's a > more rigorous definition, but let's just call it "potentially lossy in a > stable way", so that repeated application of f and g converges to an > identity after the first loss.)? With the exception of the "weird" > widening conversions that we borrowed too liberally from C, which are > called "widening" but aren't, conversions pairs that are described as > widening/narrowing pairs in JLS 5 have this property: int/long, > int/double, float/double, String/Object, etc. > > But it is really simple: c instanceof P iff the value of c is exactly > representable in type P. > > It might be tempting to say "if I can cast and cast back and get the > same answer", but unfortunately that doesn't work, because there are > lossy conversions whose inverse is also lossy in an exactly compensating > way.? _This_ would be confusing. As you said, there is a problem with the lossy conversions, and i'm confused why you want to be able to invert them given that you can not. if you prefer, float instanceof int make little sense given that the widening conversion from int to float is lossy. It's the same thing as with an unsafe cast, o instanceof List make little sense, because converting List to Object is lossy. > >> - it does not work well with Valhalla, especially with the phase 3 >> (i.e. let's pretend that a primitive type is a value type) * > > As I said, I think this is a distraction, but if you disagree, you are > going to need to provide a much more exhaustive description of how you > think Valhalla will work and why this is a conflict, than just appealing > to claims like "won't work with Valhalla."? ?Or, alternately, if you > want to focus entirely on that, then that's fine, start a new thread > (but I would expect that mail to have more "how would X work" questions > rather than assertions about how it would work.) With Valhalla, Integer is seen as int | null, with Integer! being an equivalent of int. Sadly Integer! is not int because Integer! is a subtype of Object while int is not, but we can try to provide an integration that brush that as a detail. So for the pattern matching, it would be sad if int and Integer! behave differently know that we know that we want to try to retrofit the primitive type to be value type. The problem with the semantics you propose is that the behavior of a switch on an int and the switch on an Integer! is wildly different. By example, switch(v) { case byte b -> ... case int i -> ... } if v is an int the switch compiles, or if v is an Integer (or a Integer! when we will get them) the switch does not compile. > >> - your interpretation is surprising when i've tried to explain it to >> others people (even to people coming to JUGs). > > I believe that, but as an educator, you should well know that often > "surprise" is not an indication of "wrong theory" but "wrong user mental > model", which implies that what is needed is more education. If we > consistently designed the language for least-surprise, we would very > quickly find ourselves in a pit where we cannot add anything, both > because something always surprises a 1995-era programmer, and because we > would have added layers and layers of ad-hoc "what we think the user > expects" features that we would eventually collapse under our own > weight.? (The "implicit conversion from float to int" is an example of > this kind of mistake, and look where that got us.) The problem is the inverse here, people welcome the change, the ability to use primitive types as a pattern, the problem is that the behaviors is different from what people expects. So it may make sense to have patterns that behave the way the JEP describe, but as pattern methods and not as type pattern. regards, R?mi From dan.heidinga at oracle.com Thu Sep 11 13:19:30 2025 From: dan.heidinga at oracle.com (Dan Heidinga) Date: Thu, 11 Sep 2025 13:19:30 +0000 Subject: Primitive type pattern (as actually specified) is considered harmful In-Reply-To: <2042548064.26921634.1757587501317.JavaMail.zimbra@univ-eiffel.fr> References: <2137548601.26083017.1757497165272.JavaMail.zimbra@univ-eiffel.fr> <9554891d-e2b5-4ada-83e5-da07395be702@oracle.com> <1310354903.26360960.1757514660614.JavaMail.zimbra@univ-eiffel.fr> <36774ec0-fde4-49f5-937c-76e9c73d9c76@oracle.com> <2042548064.26921634.1757587501317.JavaMail.zimbra@univ-eiffel.fr> Message-ID: Remi, I?m going to repost Brian?s earlier suggestion to start a new thread with a clearer picture of the problem you see so we can make progress here. This feels like we?ll venturing off into the weeds. I suggest that, instead of replying to individual points, you start over, and think carefully about what you are actually trying to say. Maybe you have an argument here, but all I see is a mess of "I found a confusing example, so its all wrong." Best to start over. Here?s my attempt to summarize the conversation so far in this thread. Maybe that will help to clarify the issue you see? Float and int conversions are unfortunate but they are part of the language today. Pattern matching lets them apply in more places (patterns) but can?t - and shouldn?t! - attempt to fix the sins of the past by changing those rules just in patterns. I know you have a mental model of how Integer! and int interoperate but I don?t know that it?s a finalized model. Valhalla wants to ?heal the rift? but is still working on landing the foundational building blocks for that. Trying to change the meaning of patterns today based on where you expect Valhalla to land is premature. ?Dan From: amber-spec-experts on behalf of forax at univ-mlv.fr Date: Thursday, September 11, 2025 at 6:45?AM To: Brian Goetz Cc: amber-spec-experts Subject: Re: Primitive type pattern (as actually specified) is considered harmful ----- Original Message ----- > From: "Brian Goetz" > To: "Remi Forax" > Cc: "amber-spec-experts" > Sent: Wednesday, September 10, 2025 5:02:46 PM > Subject: Re: Primitive type pattern (as actually specified) is considered harmful >> - you define "safe" as the revert operation on something which can be >> lossy (my brain can not compute that), > > Sorry about your brain, but I think this is straightforward. Given sets > A and B, with a pair of functions > > f: A -> B > g: B -> A > > we can discuss properties of the composite functions `f . g` and `g . > f`, and there are useful algebraic structures we can appeal to > (isomorphisms, embedding-projection pairs, etc) that can characterize > their behavior. For example, A and B are isomorphic if `f . g == id_B` > and `g . f == id_A`. > > Type pairs like A=int and B=long form _embedding projection pairs_, > where `g . f` is an identity function on A, and `f . g` is a > well-behaved _approximation_ of the identity function on B (there's a > more rigorous definition, but let's just call it "potentially lossy in a > stable way", so that repeated application of f and g converges to an > identity after the first loss.) With the exception of the "weird" > widening conversions that we borrowed too liberally from C, which are > called "widening" but aren't, conversions pairs that are described as > widening/narrowing pairs in JLS 5 have this property: int/long, > int/double, float/double, String/Object, etc. > > But it is really simple: c instanceof P iff the value of c is exactly > representable in type P. > > It might be tempting to say "if I can cast and cast back and get the > same answer", but unfortunately that doesn't work, because there are > lossy conversions whose inverse is also lossy in an exactly compensating > way. _This_ would be confusing. As you said, there is a problem with the lossy conversions, and i'm confused why you want to be able to invert them given that you can not. if you prefer, float instanceof int make little sense given that the widening conversion from int to float is lossy. It's the same thing as with an unsafe cast, o instanceof List make little sense, because converting List to Object is lossy. > >> - it does not work well with Valhalla, especially with the phase 3 >> (i.e. let's pretend that a primitive type is a value type) * > > As I said, I think this is a distraction, but if you disagree, you are > going to need to provide a much more exhaustive description of how you > think Valhalla will work and why this is a conflict, than just appealing > to claims like "won't work with Valhalla." Or, alternately, if you > want to focus entirely on that, then that's fine, start a new thread > (but I would expect that mail to have more "how would X work" questions > rather than assertions about how it would work.) With Valhalla, Integer is seen as int | null, with Integer! being an equivalent of int. Sadly Integer! is not int because Integer! is a subtype of Object while int is not, but we can try to provide an integration that brush that as a detail. So for the pattern matching, it would be sad if int and Integer! behave differently know that we know that we want to try to retrofit the primitive type to be value type. The problem with the semantics you propose is that the behavior of a switch on an int and the switch on an Integer! is wildly different. By example, switch(v) { case byte b -> ... case int i -> ... } if v is an int the switch compiles, or if v is an Integer (or a Integer! when we will get them) the switch does not compile. > >> - your interpretation is surprising when i've tried to explain it to >> others people (even to people coming to JUGs). > > I believe that, but as an educator, you should well know that often > "surprise" is not an indication of "wrong theory" but "wrong user mental > model", which implies that what is needed is more education. If we > consistently designed the language for least-surprise, we would very > quickly find ourselves in a pit where we cannot add anything, both > because something always surprises a 1995-era programmer, and because we > would have added layers and layers of ad-hoc "what we think the user > expects" features that we would eventually collapse under our own > weight. (The "implicit conversion from float to int" is an example of > this kind of mistake, and look where that got us.) The problem is the inverse here, people welcome the change, the ability to use primitive types as a pattern, the problem is that the behaviors is different from what people expects. So it may make sense to have patterns that behave the way the JEP describe, but as pattern methods and not as type pattern. regards, R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Sep 11 13:29:03 2025 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 11 Sep 2025 09:29:03 -0400 Subject: Primitive type pattern (as actually specified) is considered harmful In-Reply-To: <2042548064.26921634.1757587501317.JavaMail.zimbra@univ-eiffel.fr> References: <2137548601.26083017.1757497165272.JavaMail.zimbra@univ-eiffel.fr> <9554891d-e2b5-4ada-83e5-da07395be702@oracle.com> <1310354903.26360960.1757514660614.JavaMail.zimbra@univ-eiffel.fr> <36774ec0-fde4-49f5-937c-76e9c73d9c76@oracle.com> <2042548064.26921634.1757587501317.JavaMail.zimbra@univ-eiffel.fr> Message-ID: <6d992c6a-549d-415c-b835-b1f353cc7263@oracle.com> > As you said, there is a problem with the lossy conversions, and i'm confused why you want to be able to invert them given that you can not. You keep saying this, but it keeps not making sense.? Can we try to find exactly where you are getting uncomfortable (and NOT veer into unrelated Valhalla questions -- pretend null just doesn't exist for how.) 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".) 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. OK, now let's do int and double. ? ? double zero = 0; ? ? double pi = 3.14d; ? ? if (zero instanceof int i) { ... } ? ? if (pi instanceof int i) { ... } Same thing!? The first exactly encodes a number that is representable in int (i.e., could have arisen from widening an int to double), the latter does not. Before you dive into somewhere else, if you have a problem with any of these, please try to state it clearly.? Acceptable answers include: ?- yes, this is all fine, my problem is somewhere else ?- no, I have X problem with *exactly* these cases > if you prefer, float instanceof int make little sense given that the widening conversion from int to float is lossy. > > It's the same thing as with an unsafe cast, o instanceof List make little sense, because converting List to Object is lossy. > > >>> - it does not work well with Valhalla, especially with the phase 3 >>> (i.e. let's pretend that a primitive type is a value type) * >> As I said, I think this is a distraction, but if you disagree, you are >> going to need to provide a much more exhaustive description of how you >> think Valhalla will work and why this is a conflict, than just appealing >> to claims like "won't work with Valhalla."? ?Or, alternately, if you >> want to focus entirely on that, then that's fine, start a new thread >> (but I would expect that mail to have more "how would X work" questions >> rather than assertions about how it would work.) > > With Valhalla, Integer is seen as int | null, with Integer! being an equivalent of int. > Sadly Integer! is not int because Integer! is a subtype of Object while int is not, > but we can try to provide an integration that brush that as a detail. > > So for the pattern matching, it would be sad if int and Integer! behave differently know that we know that we want to try to retrofit the primitive type to be value type. > > The problem with the semantics you propose is that the behavior of a switch on an int and the switch on an Integer! is wildly different. > By example, > > switch(v) { > case byte b -> ... > case int i -> ... > } > > if v is an int the switch compiles, or if v is an Integer (or a Integer! when we will get them) the switch does not compile. > > >>> - your interpretation is surprising when i've tried to explain it to >>> others people (even to people coming to JUGs). >> I believe that, but as an educator, you should well know that often >> "surprise" is not an indication of "wrong theory" but "wrong user mental >> model", which implies that what is needed is more education. If we >> consistently designed the language for least-surprise, we would very >> quickly find ourselves in a pit where we cannot add anything, both >> because something always surprises a 1995-era programmer, and because we >> would have added layers and layers of ad-hoc "what we think the user >> expects" features that we would eventually collapse under our own >> weight.? (The "implicit conversion from float to int" is an example of >> this kind of mistake, and look where that got us.) > The problem is the inverse here, people welcome the change, the ability to use primitive types as a pattern, > the problem is that the behaviors is different from what people expects. > > So it may make sense to have patterns that behave the way the JEP describe, but as pattern methods and not as type pattern. > > regards, > R?mi From forax at univ-mlv.fr Thu Sep 11 14:30:27 2025 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 11 Sep 2025 16:30:27 +0200 (CEST) Subject: Type primitive pattern: the problem with lossy conversions Message-ID: <176027698.27128517.1757601027425.JavaMail.zimbra@univ-eiffel.fr> Following Brian and Dan comments, I restart several different threads. int to float and long to double (and long to float) are lossy conversions, for lossy conversions of object, we disallow the corresponding type pattern: For example: List list = ...; Object o = list; // this is lossy if (o instanceof List l) { // this do not compile but JEP 507 proposes to allow: int integer = ... float f = integer; // this is lossy if (f instanceof int i) {. // this compile I do not understand why we should allow the latter code but not the former ? regards, R?mi From forax at univ-mlv.fr Thu Sep 11 14:52:37 2025 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 11 Sep 2025 16:52:37 +0200 (CEST) Subject: Type primitive pattern: the problem with primitive and boxing Message-ID: <313090675.27148650.1757602357189.JavaMail.zimbra@univ-eiffel.fr> Following Brian and Dan comments, I restart several different threads. The way JEP 507 semantics is defined does not work well with the idea that int can be seen as a subtype of Integer, or at least seen int and Integer! as interoperable. For example, this code compile int v = ... switch(v) { case byte b -> ... case int i -> ... } But this code does not compile Integer v = ... switch(v) { case byte b -> ... case int i -> ... } so at best there is a risk of making our lives (the Valhalla EG) miserable in the future because of the semantics of this JEP, at worst, in the future, people will declare the component of their records either as an Integer! or as an int to get the flavor of pattern matching they want. regards, R?mi From brian.goetz at oracle.com Thu Sep 11 15:55:03 2025 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 11 Sep 2025 11:55:03 -0400 Subject: Type primitive pattern: the problem with lossy conversions In-Reply-To: <176027698.27128517.1757601027425.JavaMail.zimbra@univ-eiffel.fr> References: <176027698.27128517.1757601027425.JavaMail.zimbra@univ-eiffel.fr> Message-ID: > Following Brian and Dan comments, I restart several different threads. > > int to float and long to double (and long to float) are lossy conversions, 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. Can we do that now? From brian.goetz at oracle.com Thu Sep 11 17:37:01 2025 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 11 Sep 2025 13:37:01 -0400 Subject: Type primitive pattern: the problem with primitive and boxing In-Reply-To: <313090675.27148650.1757602357189.JavaMail.zimbra@univ-eiffel.fr> References: <313090675.27148650.1757602357189.JavaMail.zimbra@univ-eiffel.fr> Message-ID: <627f8d95-665a-4641-8596-a278624fea0f@oracle.com> I'm going to pend this thread until the other thread is resolved; I think whatever issue is confounding the other is likely to confuse this one as well. On 9/11/2025 10:52 AM, Remi Forax wrote: > Following Brian and Dan comments, I restart several different threads. > > The way JEP 507 semantics is defined does not work well with the idea that int can be seen as a subtype of Integer, or at least seen int and Integer! as interoperable. > > For example, this code compile > int v = ... > switch(v) { > case byte b -> ... > case int i -> ... > } > > But this code does not compile > Integer v = ... > switch(v) { > case byte b -> ... > case int i -> ... > } > > so at best there is a risk of making our lives (the Valhalla EG) miserable in the future because of the semantics of this JEP, at worst, in the future, people will declare the component of their records either as an Integer! or as an int to get the flavor of pattern matching they want. > > regards, > R?mi > > > > > > > > > > > > > > > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Sep 12 17:30:57 2025 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 12 Sep 2025 13:30:57 -0400 Subject: Type primitive pattern: the problem with lossy conversions In-Reply-To: References: <176027698.27128517.1757601027425.JavaMail.zimbra@univ-eiffel.fr> Message-ID: 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. > OK, now let's do int and double. > > ? ? double zero = 0; > ? ? double pi = 3.14d; > > ? ? if (zero instanceof int i) { ... } > ? ? if (pi instanceof int i) { ... } > > Same thing!? The first exactly encodes a number that is representable > in int (i.e., could have arisen from widening an int to double), the > latter does not. Remi: It could be no other way, Socrates. But we can replace double with float in the previous example, and nothing changes: > ? ? float zero = 0; > ? ? float pi = 3.14f; > > ? ? if (zero instanceof int i) { ... } > ? ? if (pi instanceof int i) { ... } Here, we are asking a sound question: does the number encoded in this `float` exactly represent an `int`.? For `zero`, the answer is "of course"; for `pi`, the answer is "obviously not." Remi: Yes, Socrates, that is clearly evident. I'm now entering guessing territory here, but I'm pretty sure I understand what's making you uncomfortable.? But, despite the clickbaity headline and misplaced claims of wrongness, this really has nothing to do with pattern matching at all!? It has to do with the existing regrettable treatment of some conversions (which we well understood are problematic, and are working on), and the fact that by its very duality, pattern matching _exposes_ the inconsistency that while most "implicit" conversions (more precisely, those allowed in method and assignment context) are required to be "safe", we allow several lossy conversions in these contexts too (int <--> float, long <--> float, long <--> double).? (The argument then makes the leap that "because this exposes a seeming inconsistency, the new feature must be wrong, and should be changed."? But it is neither fair nor beneficial to blame the son, who is actually doing it right, for the sins of the fathers.) In other words, you see these two cases as somehow so different that we should roll back several years of progress just to avoid acknowledging the inconsistency: ? ? float f = 0; ? ? if (f instanceof int i) { ... } and ? ? float g = 200_000_007;? // lossy ? ? if (g instanceof int i) { ... } because in the first case, we are merely "recovering" the int-ness of something that was an int all along, but in the second case, we are _throwing away_ some of the int value and then trying to recover it, but without the knowledge that a previous lossy operation happened. But blaming pattern matching is blaming the messenger.? Your beef is with the assignment to g, that we allow a lossy assignment without at least an explicit cast.? And this does seem "inconsistent"!? We generally go out of our way to avoid lossy conversions in assignments and method invocation (by allowing widening conversions but not narrowing ones) -- except that the conversion int -> float is considered (mystifyingly) a "widening conversion." Except there's no mystery.? This was a compromise made in 1995 borne of a desire for Java to not seem "too surprising" to C programmers.? So this conversion (and two of its irresponsible friends) were characterized as "widenings", when in fact they are not. If I could go back to 1995 and argue against this, I would.? But I can't do that.? What I can do is to acknowledge that this was a regrettable misuse of the term widening, and not extrapolate from this behavior.? Can we change the language to disallow these conversions in assignment and method context?? Unlikely, that would break way too much code.? We can, though, clarify the terminology in the JLS, and start to issue warnings for these lossy implicit conversions, and instead encourage an explicit cast to emphasize the "convert to float, dammit" intentions of the programmer (which is what we plan to do, we just haven't finalized this plan yet.)? But again, this has little to do with the proper semantics of pattern matching; it is just that pattern matching is the mirror that reveals the bad behavior of existing past mistakes more clearly.? It would be stupid to extrapolate this mistake forward into pattern matching -- that would make it both more confusing and more complicated.? Instead, we admit our past mistakes and improve the language as best as we can.? In this case, there was a pretty good answer here, despite the legacy warts. Before I close this thread, I need to reiterate that just because there is a seeming inconsistency, this is not necessarily evidence that the _most recent move_ is a mistake.? So next time, if you see an inconsistency, instead of thumping your shoe on the table and crying "mistake! mistake!", you could ask these questions instead: ?- This seems like an inconsistency, but is it really? ?- If this is an inconsistency, does that mean that one case or the other is mistake? ?- If there is a mistake, can it be fixed?? If not, should we consider changing course to avoid confusion, or are we better off living with a small inconsistency to get a greater benefit? These are the kinds of questions that language designers grapple with every day. -------------- next part -------------- An HTML attachment was scrubbed... URL: From archie.cobbs at gmail.com Fri Sep 12 18:01:47 2025 From: archie.cobbs at gmail.com (Archie Cobbs) Date: Fri, 12 Sep 2025 13:01:47 -0500 Subject: Type primitive pattern: the problem with lossy conversions In-Reply-To: References: <176027698.27128517.1757601027425.JavaMail.zimbra@univ-eiffel.fr> Message-ID: This is just an aside (I agree with everything Brian has said). I think a lot of the "uncomfortableness" comes from a simple mental model adjustment that needs to occur. If "42 instanceof float" feels normal to you, then no adjustment is needed. But I think for some people it feels funny. Why? Because those people tend to read "x instanceof Y" as "the type of x is some subtype of Y". Why is that a problem? Well, what is "the type of x"? For reference types, there is the compiler type and the runtime type, and instanceof is a way to ask about the runtime type of something that the code only knows by its compile-time type. So far, so good. But with primitives, there is no distinction between compiler type and runtime type. An "int" is always an ordered sequence of 32 bits. So applying the traditional understanding of "instanceof" leads you to this: "42 instanceof float" is obviously false, because the statement "int is some subtype of float" is false. So, int is not a subtype of float - but some ints are representable as floats. The latter property is what matters here. So perhaps when we talk about this feature, we should start by first telling everyone to replace any notion they might have that "x instanceof Y" means "type of x is some subtype of Y" with "the value x is representable as a Y". After that, the waters should become much smoother. -Archie On Fri, Sep 12, 2025 at 12:31?PM Brian Goetz wrote: > > > 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. > > OK, now let's do int and double. > > double zero = 0; > double pi = 3.14d; > > if (zero instanceof int i) { ... } > if (pi instanceof int i) { ... } > > Same thing! The first exactly encodes a number that is representable in > int (i.e., could have arisen from widening an int to double), the latter > does not. > > > Remi: It could be no other way, Socrates. > > But we can replace double with float in the previous example, and nothing > changes: > > float zero = 0; > float pi = 3.14f; > > if (zero instanceof int i) { ... } > if (pi instanceof int i) { ... } > > > Here, we are asking a sound question: does the number encoded in this > `float` exactly represent an `int`. For `zero`, the answer is "of course"; > for `pi`, the answer is "obviously not." > > Remi: Yes, Socrates, that is clearly evident. > > > I'm now entering guessing territory here, but I'm pretty sure I understand > what's making you uncomfortable. But, despite the clickbaity headline and > misplaced claims of wrongness, this really has nothing to do with pattern > matching at all! It has to do with the existing regrettable treatment of > some conversions (which we well understood are problematic, and are working > on), and the fact that by its very duality, pattern matching _exposes_ the > inconsistency that while most "implicit" conversions (more precisely, those > allowed in method and assignment context) are required to be "safe", we > allow several lossy conversions in these contexts too (int <--> float, long > <--> float, long <--> double). (The argument then makes the leap that > "because this exposes a seeming inconsistency, the new feature must be > wrong, and should be changed." But it is neither fair nor beneficial to > blame the son, who is actually doing it right, for the sins of the > fathers.) > > In other words, you see these two cases as somehow so different that we > should roll back several years of progress just to avoid acknowledging the > inconsistency: > > float f = 0; > if (f instanceof int i) { ... } > > and > > float g = 200_000_007; // lossy > if (g instanceof int i) { ... } > > because in the first case, we are merely "recovering" the int-ness of > something that was an int all along, but in the second case, we are > _throwing away_ some of the int value and then trying to recover it, but > without the knowledge that a previous lossy operation happened. > > But blaming pattern matching is blaming the messenger. Your beef is with > the assignment to g, that we allow a lossy assignment without at least an > explicit cast. And this does seem "inconsistent"! We generally go out of > our way to avoid lossy conversions in assignments and method invocation (by > allowing widening conversions but not narrowing ones) -- except that the > conversion int -> float is considered (mystifyingly) a "widening > conversion." > > Except there's no mystery. This was a compromise made in 1995 borne of a > desire for Java to not seem "too surprising" to C programmers. So this > conversion (and two of its irresponsible friends) were characterized as > "widenings", when in fact they are not. > > If I could go back to 1995 and argue against this, I would. But I can't > do that. What I can do is to acknowledge that this was a regrettable > misuse of the term widening, and not extrapolate from this behavior. Can > we change the language to disallow these conversions in assignment and > method context? Unlikely, that would break way too much code. We can, > though, clarify the terminology in the JLS, and start to issue warnings for > these lossy implicit conversions, and instead encourage an explicit cast to > emphasize the "convert to float, dammit" intentions of the programmer > (which is what we plan to do, we just haven't finalized this plan yet.) > But again, this has little to do with the proper semantics of pattern > matching; it is just that pattern matching is the mirror that reveals the > bad behavior of existing past mistakes more clearly. It would be stupid to > extrapolate this mistake forward into pattern matching -- that would make > it both more confusing and more complicated. Instead, we admit our past > mistakes and improve the language as best as we can. In this case, there > was a pretty good answer here, despite the legacy warts. > > Before I close this thread, I need to reiterate that just because there is > a seeming inconsistency, this is not necessarily evidence that the _most > recent move_ is a mistake. So next time, if you see an inconsistency, > instead of thumping your shoe on the table and crying "mistake! mistake!", > you could ask these questions instead: > > - This seems like an inconsistency, but is it really? > - If this is an inconsistency, does that mean that one case or the other > is mistake? > - If there is a mistake, can it be fixed? If not, should we consider > changing course to avoid confusion, or are we better off living with a > small inconsistency to get a greater benefit? > > These are the kinds of questions that language designers grapple with > every day. > > > -- Archie L. Cobbs -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Sep 12 18:32:06 2025 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 12 Sep 2025 14:32:06 -0400 Subject: Type primitive pattern: the problem with lossy conversions In-Reply-To: References: <176027698.27128517.1757601027425.JavaMail.zimbra@univ-eiffel.fr> Message-ID: Indeed so.? Some people are having a hard time making the shift away from `instanceof` as being purely a type query.? And this is one of the costs of the decision we made to "lump" vs "split" in the surface syntax when we added patterns at all; we had a choice of picking a new keyword (e.g., "matches") or extending instanceof to support patterns.? This choice had pros and cons on both sides of the ledger, and having made a choice, we now have to live with whatever cons that choice had.? (FTR I am completely convinced that this was the better choice, but that doesn't make the cons go away.) In C#, they spelled their `instanceof` operator `is`, and in that world, the "this is only about types" interpretation is less entrenched, for purely syntactic reasons; both ? ? if (anObject is String) and ? ? if (aFloat is int) seems pretty natural.? It will take some time for people to reprogram this accidental association, but once they do, it won't be a problem. On 9/12/2025 2:01 PM, Archie Cobbs wrote: > This is just an aside (I agree with everything?Brian has said). > > I think?a lot of the "uncomfortableness" comes from a simple mental > model adjustment that needs to occur. > > If "42 instanceof float" feels normal to you, then no adjustment is > needed. But I think for some people it feels funny. > > Why? Because those people tend to read "x instanceof Y" as "the type > of x is some?subtype of Y". > > Why is that a problem? Well, what is "the type of x"? For reference > types, there is the compiler type and the?runtime type, and?instanceof > is a way to ask about the runtime type of something that the code only > knows by its compile-time type. So far, so good. > > But with primitives, there is no distinction between compiler type and > runtime type. An "int" is always an ordered sequence of 32 bits. > > So applying the traditional understanding of "instanceof" leads you to > this: "42 instanceof float" is obviously false, because?the statement > "int is some subtype of float" is false. > > So, int is not a subtype of float - but some ints?are representable as > floats. The latter property is what matters here. > > So perhaps when we talk about this feature, we should start by first > telling everyone to replace any notion they might have that "x > instanceof Y" means "type of x is some subtype of Y" with "the value x > is representable as a Y". After that, the waters should become much > smoother. > > -Archie > > > On Fri, Sep 12, 2025 at 12:31?PM Brian Goetz > wrote: > > > > 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. > >> OK, now let's do int and double. >> >> ? ? double zero = 0; >> ? ? double pi = 3.14d; >> >> ? ? if (zero instanceof int i) { ... } >> ? ? if (pi instanceof int i) { ... } >> >> Same thing!? The first exactly encodes a number that is >> representable in int (i.e., could have arisen from widening an >> int to double), the latter does not. > > Remi: It could be no other way, Socrates. > > But we can replace double with float in the previous example, and > nothing changes: > >> ? float zero = 0; >> ? ? float pi = 3.14f; >> >> ? ? if (zero instanceof int i) { ... } >> ? ? if (pi instanceof int i) { ... } > > Here, we are asking a sound question: does the number encoded in > this `float` exactly represent an `int`.? For `zero`, the answer > is "of course"; for `pi`, the answer is "obviously not." > > Remi: Yes, Socrates, that is clearly evident. > > > I'm now entering guessing territory here, but I'm pretty sure I > understand what's making you uncomfortable.? But, despite the > clickbaity headline and misplaced claims of wrongness, this really > has nothing to do with pattern matching at all!? It has to do with > the existing regrettable treatment of some conversions (which we > well understood are problematic, and are working on), and the fact > that by its very duality, pattern matching _exposes_ the > inconsistency that while most "implicit" conversions (more > precisely, those allowed in method and assignment context) are > required to be "safe", we allow several lossy conversions in these > contexts too (int <--> float, long <--> float, long <--> double).? > (The argument then makes the leap that "because this exposes a > seeming inconsistency, the new feature must be wrong, and should > be changed."? But it is neither fair nor beneficial to blame the > son, who is actually doing it right, for the sins of the fathers.) > > In other words, you see these two cases as somehow so different > that we should roll back several years of progress just to avoid > acknowledging the inconsistency: > > ? ? float f = 0; > ? ? if (f instanceof int i) { ... } > > and > > ? ? float g = 200_000_007;? // lossy > ? ? if (g instanceof int i) { ... } > > because in the first case, we are merely "recovering" the int-ness > of something that was an int all along, but in the second case, we > are _throwing away_ some of the int value and then trying to > recover it, but without the knowledge that a previous lossy > operation happened. > > But blaming pattern matching is blaming the messenger. Your beef > is with the assignment to g, that we allow a lossy assignment > without at least an explicit cast.? And this does seem > "inconsistent"!? We generally go out of our way to avoid lossy > conversions in assignments and method invocation (by allowing > widening conversions but not narrowing ones) -- except that the > conversion int -> float is considered (mystifyingly) a "widening > conversion." > > Except there's no mystery.? This was a compromise made in 1995 > borne of a desire for Java to not seem "too surprising" to C > programmers.? So this conversion (and two of its irresponsible > friends) were characterized as "widenings", when in fact they are > not. > > If I could go back to 1995 and argue against this, I would.? But I > can't do that.? What I can do is to acknowledge that this was a > regrettable misuse of the term widening, and not extrapolate from > this behavior.? Can we change the language to disallow these > conversions in assignment and method context?? Unlikely, that > would break way too much code.? We can, though, clarify the > terminology in the JLS, and start to issue warnings for these > lossy implicit conversions, and instead encourage an explicit cast > to emphasize the "convert to float, dammit" intentions of the > programmer (which is what we plan to do, we just haven't finalized > this plan yet.)? But again, this has little to do with the proper > semantics of pattern matching; it is just that pattern matching is > the mirror that reveals the bad behavior of existing past mistakes > more clearly.? It would be stupid to extrapolate this mistake > forward into pattern matching -- that would make it both more > confusing and more complicated.? Instead, we admit our past > mistakes and improve the language as best as we can.? In this > case, there was a pretty good answer here, despite the legacy warts. > > Before I close this thread, I need to reiterate that just because > there is a seeming inconsistency, this is not necessarily evidence > that the _most recent move_ is a mistake.? So next time, if you > see an inconsistency, instead of thumping your shoe on the table > and crying "mistake! mistake!", you could ask these questions instead: > > ?- This seems like an inconsistency, but is it really? > ?- If this is an inconsistency, does that mean that one case or > the other is mistake? > ?- If there is a mistake, can it be fixed?? If not, should we > consider changing course to avoid confusion, or are we better off > living with a small inconsistency to get a greater benefit? > > These are the kinds of questions that language designers grapple > with every day. > > > > > -- > Archie L. Cobbs -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Sep 12 20:57:39 2025 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 12 Sep 2025 16:57:39 -0400 Subject: Type primitive pattern: the problem with primitive and boxing In-Reply-To: <313090675.27148650.1757602357189.JavaMail.zimbra@univ-eiffel.fr> References: <313090675.27148650.1757602357189.JavaMail.zimbra@univ-eiffel.fr> Message-ID: <179e5e81-7d60-4ec9-808e-646ca5f72387@oracle.com> Coming back to Remi's other question. As a reminder, pattern matching has _almost no independent semantics_; its semantics are derived from that of conversions (and casting context.) The second code does not compile _today_, because, while there is a conversion from byte to int, there is no conversion from byte to Integer (specifically, a primitive widening followed by a boxing operation.)? Valhalla will introduce new conversions, such as `Foo! <--> Foo?` conversions, and possibly (as was discussed at JVMLS) user-defined value widening and narrowing conversions. All of this will require some revamp of Conversions and Contexts, including both restructuring of the presentation and adding some new conversions.? At the same time, irregularities like why is "PW+Box not in the same bucket as other unconditional conversions" can be addressed. So yet again, this has absolutely nothing to do with the proposed semantics of type patterns; it has everything to do (again) with oddities and irregularities in the conversions story -- which can (and will) be improved. And, just as with the previous, the wild leap from "something is imperfect now" to "OMG you are breaking the world" was ... overstated.??So I will reiterate the same reminder as the previous: > So next time, if you see an inconsistency, instead of thumping your > shoe on the table and crying "mistake! mistake!", you could ask these > questions instead: > > ?- This seems like an inconsistency, but is it really? > ?- If this is an inconsistency, does that mean that one case or the > other is mistake? > ?- If there is a mistake, can it be fixed?? If not, should we consider > changing course to avoid confusion, or are we better off living with a > small inconsistency to get a greater benefit? On 9/11/2025 10:52 AM, Remi Forax wrote: > Following Brian and Dan comments, I restart several different threads. > > The way JEP 507 semantics is defined does not work well with the idea that int can be seen as a subtype of Integer, or at least seen int and Integer! as interoperable. > > For example, this code compile > int v = ... > switch(v) { > case byte b -> ... > case int i -> ... > } > > But this code does not compile > Integer v = ... > switch(v) { > case byte b -> ... > case int i -> ... > } > > so at best there is a risk of making our lives (the Valhalla EG) miserable in the future because of the semantics of this JEP, at worst, in the future, people will declare the component of their records either as an Integer! or as an int to get the flavor of pattern matching they want. > > regards, > R?mi > > > > > > > > > > > > > > > > -------------- next part -------------- An HTML attachment was scrubbed... URL: