Let me try and put some more color on the bike shed (but, again, let’s focus on 
model, not syntax, for now.)

We have two axes of variation we want to express with non-identity classes: 
atomicity constraints, and whether there is an additional zero-default 
companion type.  These can be mostly orthogonal; you can have either, neither, 
or both.  We've been previously assuming that "primitiveness" lumps this all 
together; primitives get more flattening, primitives can be 
non-nullable/zero-default, primitives means the good name goes to the "val" 
type.  Primitive-ness implicitly flips the "safety vs performance" priority, 
which has been bothering us because primitives also code like a class.  So we 
were trying to claw back some atomicity for primitives.

But also, we're a little unhappy with B2 because B2 comes with _more_ atomicity 
than is necessarily needed; a B2 with no invariants still gets less flattening 
than a B3.  That's a little sad.  And also that it seems like a gratuitous 
difference, which makes the user model more complicated.  So we’re suggesting 
restacking towards:

- Value classes are those without identity
- Value classes can be atomic or non-atomic, the default is atomic (safe by 
default)
- Value classes can further opt into having a "val" projection (name TBD, val 
is probably not it)
- Val projections are non-nullable, zero-default — this is the only difference
- Both the ref and val projections inherit the atomicity constraints of the 
class, making atomicity mostly orthogonal to ref/val/zero/null

Example: classic B2

  value class B2a { }

Because the default is atomic, we get the classic B2 semantics -- no identity, 
but full final field safety guarantees.  VM has several strategies for 
flattening in the heap: single-field classes always flattened (“full flat”), 
multi-field classes can be flattened with "fat load and store" heroics in the 
future (“low flat”), otherwise, indirection (“no flat”)

Example: non-atomic B2

  non-atomic value class B2n { }

Here, the user has said "I have no atomicity rquirements."  A B2n is a loose 
aggregation of fields that can be individually written and read (full B3-like 
flattening), with maybe an extra boolean field to encode null (VM's choice how 
to encode, could use slack pointer bits etc.)

Example: atomic B3

  zero-capable value class B3a { }

This says I am declaring two types, B3a and B3a.zero.  (The syntax in this 
quadrant sucks; need to find better.)  B3a is just like B2a above, because we 
haven’t activated the zero capability at the use site.  
B3a.zero/val/flat/whatever is non-nullable, zero-default, *but still has full 
B2-classic atomicity*.  With the same set of flattening choices on the part of 
the VM.

Example: full primitive

  non-atomic zero-capable value class B3n { }

Here, B3n is like B2n, and B3n.zero is a full classic-B3 Q primitive with full 
flattening.

So:

- value-ness means "no identity, == means state equality"
- You can add non-atomic to value-ness, meaning you give up state integrity
- You can orthogonally add zero-capable to value-ness, meaning you get a 
non-null, zero-happy companion, which inherits the atomic-ness

Some of the characteristics of this scheme:

- The default is atomicity / integrity FOR ALL BUCKETS (safe by default)
- The default is nullability FOR ALL BUCKETS
- All unadorned type names are reference types / nullable
- All Val-adorned type names (X.val) are non-nullable (or .zero, or .whatever)
- Atomicity is determined by declaration site, can’t be changed at use site

The main syntactic hole is finding the right spelling for "zeroable" / .val.  
There is some chance we can get away with spelling it `T!`, though this has 
risks.

Spelling zero-happy as any form of “flat” is probably a bad idea, because B2 
can still be flat.

A possible spelling for “non-atomic” is “relaxed”:

  relaxed value class B3n { }

Boilerplate-measurers would point out that to get full flattening, you have to 
say three things at the declaration site and one extra thing at the use site:

   relaxed zero-happy value class Complex { }
   …
   Complex! c;

If you forget relaxed, you might get atomicity (but might not cost anything, if 
the value is small.)  If you forget zero-happy, you can’t say `Complex!`, you 
can only say Complex, and the compiler will remind you.  If you forget the !, 
you maybe get some extra footprint for the null bit.  None of these are too 
bad, but the verbosity police might want to issue a warning here.

It is possible we might want to flip the declaration of zero-capable, where 
classes with no good default can opt OUT of the zero companion, rather than the 
the other way around:

   null-default value class LocalDate { }

which says that LocalDate must use the nullable (LocalDate) form, not the 
non-nullable (LocalDate.val/zero/bang) form.

Reply via email to