Yes, i know, we have already discuss several models like that. But i think,
it's a good idea to re-examine those because i believe they are more attractive
today.
My aim here is to try to simplify the .ref/.val model, not to fundamentally
change it, .ref is still the default, there is no way to ask for a .val at
definition site, etc so most of the design should be very familiar.
The main issue with the .val model is that it presents two *types* to the user
while we really want is mostly to flatten the storage and have a precise the
method calling convention.
Those two goals are not equals, the first is far more important than the
second, to the point where the coding guideline proposed by Brian is to use
.ref for the parameters and .val for the fields and arrays.
We still need .val and .ref to be able to specialize generics, right ? No, i
don't think so, we technically do not have to pass a .val as type argument to
be able to specialize a generic class, we just need to pass a type argument
that can be flatten if it's possible.
Let's run this idea, let say that if we have a value class C, we do not need to
pass C.val as argument to specialize a List, List<C> is enough.
Then for field/array and parameter, we need to introduce a storage hint saying
that the value class must be a Q-type, in the rest of the document, i will use
.flat for that.
so instead of writing
value class C {
// ...
}
class Container<T> {
private T value;
public Container(T value) {
this.value = value;
}
}
...
Container<C.val> container = new Container<C.val>(new C());
we can write instead
value class C {
// ...
}
class Container<T> {
private T.flat value;
public Container(T value) {
this.value = value; // may NPE
}
}
...
Container<C> container = new Container<C>(new C()); // there is no C.val
anymore !
The idea is to align the way, we declare a generics class with the way we
declare a classical class, i.e. instead of specializing the T using a C.val as
type argument, we can directly use C as type argument and ask for the flattened
version of T using T.flat. This is very similar to the way the equivalent
non-generics class is currently declared in the .ref/.val model if you replace
replace .flat by .val.
class Container {
private C.flat value;
public Container(C value) {
this.value = value; // may NPE
}
}
So it appears that if we do not allow users to specify if a local variable is a
.ref or a .val but decide that it's always a .ref and if we are using container
hint inside generics classes the same way we are using it on non-generics
classes, then we do not need .ref and .val to be types, but only to be storage
hints.
And not having .ref and .val to be types greatly simplify the model, because
they is no interaction between the type checking and the storage hints, those
are two separated concerns.
So following the actual design, they are 3 différents kind of value class,
using encapsulation to declare if a default is available or not
value class C {
private default {}
}
value class C {
/* package-private */ default {}
}
value class C {
public default {}
}
I've used "default" instead of "companion type" here because those are not type
anymore.
If flatten, the assignment can be non-atomic or atomic, with atomic being the
default:
non-atomic value class NC {
[modifier] default {}
}
Because there is only one type C, all the syntax C.class, c.getClass(),
instanceof C, etc works as usual.
All Codes that does not use C.flat works as usual, apart from ==, hashCode,
synchronized and weak reference.
The storage hint C.flat can be used only in few selected places:
- as a hint on a field type:
private C.flat c;
- as a hint on a field array type:
private C.flat[] c;
- when creating an array
new C.flat[16]
- as a hint on a parameter type of a method
void foo(C.flat c) { ... }
In terms of code generation, .flat is equivalent to asking a Q-type and adding
a checkcast (or equivalent) from the Q-type to the L-type (or vice-versa) for
each read/write of the field, the array cell or the parameter (for the
parameter it can be done once).
I really think that the .flat model is far easier to use than the .ref/.val
model because it untangle the notion of value type with the notion of container
hints and it seems to me that a JIT able to propagate/merge the Q-types should
generate an assembly code as efficient as with the .ref/.val model.
And obviously, i may have forgotten something invalidating the whole design,
please shoot.
regards,
Rémi