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

Reply via email to