[External] : Re: Consolidating the user model

Dan Smith daniel.smith at oracle.com
Thu Nov 4 16:28:13 UTC 2021


On Nov 3, 2021, at 6:19 PM, Kevin Bourrillion <kevinb at google.com<mailto:kevinb at google.com>> wrote:

I think my intuitions about boxes tie heavily to 'getClass' behavior (or some analogous reflective operation). "What are you?" should give me different answers for a bare value and a box. A duck in a box is not the same thing as a duck.

The analogy here would be that Integer.getClass() returns Integer.class, while int.getClass(), if it existed, would return int.class.

So far so good. If `int.getClass()` has to work at all, it might as well produce `int.class`, though it serves no actual purpose and we would just refactor it to `int.class` anyway. If `int.getClass()` won't even compile, it would be no great loss at all. The method exists for finding the dynamic type of an object; my model says "values are not objects and so have no dynamic type", which I think is good.

But Point extends Object, and Object.getClass exists.

One thing the user model has to explain is how method inheritance works. You've been pointing out that inheritance != subtyping, which is true. But still, when I invoke a super method (a default method in a superinterface, say), it must be true that that method declaration knows how to execute on a value.

The ref/val model explains this by saying that method invocation will add/remove references to align with the expecations of the (dynamically-selected) method implementation. The object remains the same, so 'this' is the object that the caller started with.

I guess the value/object model would pretty much say the same thing, except it would say the value the caller started with might be boxed (or the object unboxed) to match the method's expectations. It's the same *value*, presented as an object.

Either way, if I can invoke 'getClass', its behavior is specified by the *class* not the value/object, so I would expect to get the same answer whether invoked via a value or a reference/box.

(Another thing you could say is that the super method is like a template, stamped out in specialized form for each primitive subclass as part of inheritance. We experimented with this way of thinking for awhile before deciding, no, it really needs to be the case that invoking an inherited method means executing the method body in its original context.)

Now, all that said, we could say by fiat that `getClass` is special and value types aren't allowed to invoke it. YAGNI. Except...

I might want to write code like:

<T extends Point.ref> void m(T arg) {
    if (arg.getClass() == Point.class) System.out.println("I'm a value!");
    else System.out.println("I'm a box!");
}

Someone might think this, but they can just ask themselves whether `int/Integer` work like that. They don't, so this doesn't either.

int/Integer are a starting point, but our goal is to offer something more.

In particular, we want universal generics: when I invoke m and pass it a Point, it must be the case that T=Point, not T=Point.ref. This is different than the status quo for int/Integer, where T=Integer.

The right way to interpret generic code is, roughly, to substitute [T:=Point] and figure out what the code would do. This is imprecise, because there are compile-time decisions that aren't allowed to change under different substitutions. (For example, we don't re-do overload resolution for different Ts, even if it would get different answers.) But, for our purposes, it should be the case that you can imagine 'arg' being a value, not a reference, and this code having intuitive behavior.

So the ref/val model says that 'arg' is an object (handled by value, not by reference) and its 'getClass' method returns the class of the object.

The value/object model says that 'arg' is a value and its 'getClass' method exists. And I guess it returns Point.class.

(If we really thought `getClass` was poison, I guess at this point we could say by fiat that type variable types aren't allowed to access `getClass`. But... `getClass` really is a useful thing to invoke in this context.)

An implication of universal generics is that there needs to be some common protocol that works on both vals and refs. In the val/ref model, that protocol is objects: both vals and refs are objects with members that can be accessed via '.'. In the value/object model, I'm not quite sure how you'd explain it. Maybe there's a third concept here, generalizing how values and objects behave.



More information about the valhalla-spec-observers mailing list