[External] Foo / Foo.ref is a backward default; should be Foo.val / Foo
Brian Goetz
brian.goetz at oracle.com
Mon Apr 25 20:31:07 UTC 2022
I think this is getting out of hand.
Kevin presented a carefully thought out argument — which I am sure he spent many hours on before sending — about why we might have (yet again) gotten the defaults wrong. I think we owe it to Kevin (and all the Java developers) to think as carefully about his observations as he did before he sent them; blasting back with knee-jerk “but this is the answer” is not helpful. We’re not finishing designing the user-facing part of this, and we dare not close our minds to the points Kevin has raised, whether or not the eventual answer goes that way or not.
On Apr 25, 2022, at 4:13 PM, Remi Forax <forax at univ-mlv.fr<mailto:forax at univ-mlv.fr>> wrote:
________________________________
From: "Kevin Bourrillion" <kevinb at google.com<mailto:kevinb at google.com>>
To: "valhalla-spec-experts" <valhalla-spec-experts at openjdk.java.net<mailto:valhalla-spec-experts at openjdk.java.net>>
Sent: Monday, April 25, 2022 4:52:50 AM
Subject: [External] Foo / Foo.ref is a backward default; should be Foo.val / Foo
Hi,
The current plan for `primitive class Foo` -- to call the value type `Foo` and the reference type `Foo.ref` -- is causing a few problems that I think are unnecessary. I've felt for a while now that we are favoring the wrong default. We should let `Foo` be the reference type and require `Foo.val` (precise syntax aside) for the value type.
I started to list reasons and came up with more than expected.
If ref is the default for B3 then B3 is a worst B2, it's like saying let's transform all long to Long.
1. The option with fewer hazards should usually be the default. Users won't opt themselves into extra safety, but they will sometimes opt out of it. Here, the value type is the one that has attendant risks -- risk of a bad default value, risk of a bad torn value. We want using `Foo.val` to *feel like* cracking open the shell of a `Foo` object and using its innards directly. But if it's spelled as plain `Foo` it won't "feel like" anything at all.
Users should use B2 by default, you did agree about that.
if users want B3 we should give them B3, asking for B3.val is a kind of double opt-in.
2. In the current plan a `Foo.ref` should be a well-behaved bucket 2 object. But it sure looks like that `.ref` is specifically telling it NOT to be -- like it's saying "no, VM, *don't* optimize this to be a value even if you can!" That's of course not what we mean. With the change I'm proposing, `Foo.val` does make sense: it's just saying "hey runtime, while you already *might* have represented this as a value, now I'm demanding that you *definitely* do". That's a normal kind of a thing to do.
.ref should be rare in the end, it's mostly a stopgap measure because we do not have universal generics.
Once we have universal generics, Foo.val make even less sense.
3. This change would permit compatible migration of an id-less to primitive class. It's a no-op, and use sites are free to migrate to the value type if and when ready. And if they already expose the type in their API, they are free to weigh the costs/benefits of foisting an incompatible change onto *their* users. They have facilities like method deprecation to do it with. In the current plan, this all seems impossible; you would have to fix all your problematic call sites *atomically* with migrating the class.
B3 requires that the default value that makes sense and that bypassing the constructor is fine (because you can construct any values by "merging" existing values).
Maybe we should disallow users to even write constructors to avoid to get them false hope.
Anyway, those constraints mean that you will not be able to refactor most of the existing classes to a primitive classes because you are loosing encapsulation by doing that.
4. It's much (much) easier on the mental model because *every (id-less) class works in the exact same way*. Some just *also* give you something extra, that's all. This pulls no rugs out from under anyone, which is very very good.
No , B2 and B3 are different runtime models, even if B3 is ref by default.
The idea of B3 being ref by default is infact dangerous exactly for the reason you explain, it looks like the two models are the same.
The problem is that they are not.
5. The two kinds of types have always been easily distinguishable to date. The current plan would change that. But they have important differences (nullability vs. the default value chief among them) just as Long and long do, and users will need to distinguish them. For example you can spot the redundant check easily in `Foo.val foo = ...; / requireNonNull(foo);`.
You want a use site way to see if a type is a B3 as opposed to B1 and B2 that are both nullable.
It's something that can be discussed separately.
6. It's very nice when the *new syntax* corresponds directly to the *new thing*. That is, until a casual developer *sees* `.val` for the first time, they won't have to worry about it.
But it's not true, compare
Complex.val c = new Complex(1, 2);
and
Complex c = new Complex(1, 2);
7. John seemed to like my last fruit analogy, so consider these two equivalent fruit stand signs:
a) "for $1, get one apple OR one orange . . . with every orange purchased you must also take a free apple"
b) "apples $1 . . . optional free orange with each purchase"
Enough said I think :-)
8. The predefined primitives would need less magic. `int` simply acts like a type alias for `Integer.val`, simple as that. This actually shows that the whole feature will be easier to learn because it works very nearly how people already know primitives to work. Contrast with: we hack it so that what would normally be called `Integer` gets called `int` and what normally gets called `Integer.ref` or maybe `int.ref` gets called `Integer` ... that is much stranger.
int can not be an alias of Integer.val, it's a little more complex than that,
by example
int.class.getName().equals("int")
but at the same time,
we want ArrayList<int> to be ArrayList<Qjava/lang/Integer>.
What are the opposing arguments?
The runtime model of B2 and B3 are not the same. Defaulting B3 to B3.ref make things dangerous because users will have trouble to see how B2 and B3 are different at runtime.
Rémi
More information about the valhalla-spec-observers
mailing list