one class, two types, many bikesheds

John Rose john.r.rose at oracle.com
Thu Jul 21 23:00:49 UTC 2022


On 19 Jul 2022, at 10:54, Kevin Bourrillion wrote:

> …My own answer to the question has been
> Understanding
> classes and types in Java
> <https://docs.google.com/document/d/1G5dNQ0kQwA5zefGdP_nvFJByb63QNlz0XiSjltiJM84/edit>
> (comments
> welcome).
> I see a class as a way to "configure" and feed behavior into a type 
> (apart
> from classes *also* serving as bundles of static members). It's a 
> fairly
> subservient relationship, which feels right to me.

Yes, I like your document (at about a 90% level).  It’s helpful to 
mention it here.

I think it’s a good observation that static members are properties 
only of classes while non-static members are primarily properties of 
types.

(All members are in classes to start with but in your document 
non-statics quickly lift to types in practical usage.  There are surely 
times when members stay in their class.  Reflection comes to mind.)

I also like your observation that the principal type of a class can be 
obtained by asking for the type of `this`.  It’s clean.

It might be too clean for us, in fact, because the observation is likely 
to clash with some very different but also reasonable expectations, 
which would ask that the principal type of a class be the meaning of `C` 
(unadorned by `.ref` or `.val`), or that the principal type of a class 
be represented by the mirror which you get when you call 
`Object::getClass` on an instance.

I think I would like all of the above questions to be answered by 
`C.ref`, even though there are plenty of times when someone will propose 
or expect that `C.val`, or something else, is the answer.

(Oh, and please please do not have `Object::getClass` return different 
values for variables of type `C.ref` and `C.val`; I think I see that 
suggested from time to time!)

Your appeal to `this` has a special benefit when `C` is generic:  It 
captures the most general type possible, of the form `C<T>`.

(Except for conditional methods if we ever do those; then `this` might 
have a conditional bound on a parameter.  The root problem is that `T` 
will probably mean something subtly different in a conditional method, 
so `C<T>` doesn’t mean just one type everywhere in `C`.)

> (Sorry for digression: you could also say one class engenders many 
> array
> types, though. I think it helps to fully distinguish predefined,
> user-defined, and composed types. Setting aside value classes 
> temporarily:
> each class directly defines just one type, which is the type of `this`
> inside the class itself (the "implicit type", or the "this-type"). 
> That's
> the all-important type whose member signatures are seen in the class 
> and
> whose supertypes are seen in the class signature. Other types can be
> composed out of the defined types: array types, type variables,
> intersection types I guess, and relevant to us here, all *other*
> parameterized types beyond the implicit type. That is, imho it's most
> fruitful to understand those parameterized types as deriving from the
> implicit type/"this-type", with member signatures and supertypes being
> calculated from that implicit type via substitution, rather than to 
> see
> them all as popping directly off of the generic class.)

Yup, when is a related type a true companion, and when is it just a 
projection?  We get to define this, and then we have to live with it.

It’s an interesting outcome (of your `this` position) that `C<T>`, out 
of all the generic instances of `C`, is elevated to principal position, 
and all other `C<U>` are mere projections of `C`.

(Surely you already considered and rejected the following alternative 
choice of narrative in your document, which I will state here FTR:  The 
principal type of `C`, when `C` is generic, is its *raw type*.  That is 
much less useful for speaking about the type of expressions derived from 
`this`, but it aligns much more closely with the other “questions” I 
alluded to above:  “What is the type denoted by merely the class name 
`C`?”  And “What is the mirror returned from `Object::getClass` when 
invoked on an instance of class `C`?”)

> I see some sense in your argument, but I still can't think of a reason 
> I'd
> want to see `ClassName.ref` in source code. It seems like that can't 
> add
> any information.

I mean it can adds a certain connotation (“stylized clarity” as I 
said) to the code.  Have you ever written a fully-qualified name where 
it wasn’t necessary?  (I have, when I wanted to emphasize where the 
symbol came from:  Such emphasis is connotation not denotation.)  Have 
you ever written `public` on an interface member where you didn’t need 
to?  Again, I’d call that choice a matter of stylized clarity.

Depending on how type inference works, `ClassName.ref` vs `ClassName` 
might affect TI, as `List<ClassName.ref>` vs. `List<ClassName>`.  This 
is, I think, the case with certain drafts of Valhalla-related generics.

I made a sly reference to null-inference.  As with type inference, I 
could imagine designs of NI where `ClassName.ref` vs `ClassName` 
produces a different inference about null.  Suppose there’s some way 
of saying, for `ju.Optional`, that only a dope would make null values of 
that reference type.  Then `Optional.ref` could possibly be a way of 
saying, “I’m that dope, bear with me.”

>>>>  - Maybe: For any type variable T (in specialized generics?), T.val
>>    also names a type.
>>
>>
> If I ever see `T.val` (except maybe the case of `T.val[]`??) I will 
> assume
> some kind of templating must be going on, since we'll all have learned
> early on that there is no polymorphic interaction with values. Is that 
> your
> expectation too?

I’m imagining, at least, some sort of additional “leakage” of 
ref/val distinctions into the scope of `T`.  We have such leakage 
already otherwise `T.ref` wouldn’t be useful; it happens when a 
generic API is bound to type arguments and `T` looks like `C.val`.  I 
think the consensus is that the use cases don’t support doing the 
reverse, of allowing `T.val` to mean `C.val` when `T` is `C.ref`, but 
it’s logically possible isn’t it?  And if so a use case may show up.

Independently, something like what Remi discusses, of flattening to 
val-type inside a generic bound to a ref-type, could be a use of 
`T.val`. I think you surmised that: `new T.val[n]` could be an 
optimistic dynamic buffer, if the actual type `C.val[]` were somehow 
available at that point.  That would require even more “leakage” of 
information about type arguments beyond the API of a generic and into 
its method bodies and maybe even field types.  Eventually you would use 
such information to “fill in templates”, including flattening fields 
to `C.val`.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/valhalla-spec-observers/attachments/20220721/b6b89a0d/attachment-0001.htm>


More information about the valhalla-spec-observers mailing list