User model stacking: current status

Brian Goetz brian.goetz at oracle.com
Mon Jun 27 18:48:07 UTC 2022


I've been bothered by an uncomfortable feeling that .val and ! are 
somehow different in nature, but haven't been able to put my finger on 
it.  Let me make another attempt.

The "bang" and "question" operators operate on types.  In the strictest 
form, the bang operator takes a type that has null in its value set, and 
returns a type whose value set is the same, except for null.   But 
observe that if the value set contains null, then the type has to be a 
reference type.  And the resulting type also has to be a reference type 
(except maybe for weird classes like Void) because we're preserving the 
remaining values, which are references.  So we could say:

     bang :: RefType -> RefType

Bang doesn't change the ref-ness, or id-ness, of a type, it just 
excludes a specific value from the value set.

Now, what do ref and val do?  They don't operate on types, they operates 
on _classes_, to produce a type.  Val can only be applied to value 
classes, and produces a value type.  In the strictest interpretation 
(for consistency with bang), ref also only operates on value classes.  So:

     val :: ValClass -> ValType
     ref :: ValClass -> RefType

Now, we've been strict with bang and ref to say they only work when they 
have a nontrivial effect, and could totalize them in the obvious way 
(ref is a no-op on an id class; bang is a no-op on a value type.)  Which 
would give us:

     bang :: Type -> Type
     val :: ValClass -> ValType
     ref :: Class -> RefType

with the added invariant that bang preserves id-ness/val-ness/ref-ness 
of types.

But still, bang and ref operate on different things, and and produce 
different things; one takes a type and yields a slightly refined type 
with similar characteristics, the other takes a class and yields a type 
with highly specific characteristics.  We can conclude a lot from `val` 
(its a value type, which already says a lot), but we cannot conclude 
anything other than  non-nullity from `bang`; it might be a ref or a val 
type, it might come from an identity or value class.

What this says to me is "val is a subtype of bang"; all vals are bangs, 
but not all bangs are vals.

A harder problem is what to do about `question`.  The strict 
interpretation says we can only apply `question` to a type that is 
already non-null.  In our world, that's ValType.

     question :: ValType -> Type

Or we could totalize as we did with bang, and we get an invariant that 
question preserves id-ness, val-ness, ref-ness.  But, what does 
`question` really mean?  Null is a reference. So there are two 
interpretations: that question always yields a reference type (which 
means non-references need to be lifted/boxed), or that question yields a 
union type.

It turns out that the latter is super-useful on the stack but kind of 
sucks in the heap.  The return value of `Map::get`, which we've been 
calling `T.ref`, really wants a union type (T or Null); similarly, many 
difficult questions in pattern matching might be made less difficult 
with a `T or Null` Type.  But there is no efficient heap-based 
representation for such a union type; we could use tagged unions (blech) 
or just fall back to boxing.  Which leaves us with the asymmetry that 
bang is representation-preserving (as well as other things), but 
question is not.  (Which makes sense in that one is subtractive and the 
other is additive.)

So, to your question: is this permanently gross?  I think if we adopt 
the strictest intepretations:

  - bang is only allowed on types that are already nullable
  - question is only allowed on types that are not nullable (or on type 
variables)
  - val is only allowed on value classes
  - ref is only allowed on value classes (or on type variables)

(And we can possibly boil away the last one, since if we can say `T?`, 
there is no need for `T.ref` anywhere.)

What this means is that you can say `String!`, but not `Optional!`, 
because Optional is already null-free.  Which means there is never any 
question whether you say `X.val` or `X!` or `X.val!` (or `X.ref!` if we 
exclude ref entirely).  So then, rather than two ways to say the same 
thing, there are two ways to say two different things, which have 
different absolute strengths.

This is somewhat unfortunate, but not "permanently gross."

If we drop `ref` in favor of `?` (not necessarily a slam-dunk), we can 
consider finding another way to spell `.val` which is less intrusive, 
though there are not too many options that don't look like line noise.





On 6/15/2022 12:41 PM, Kevin Bourrillion wrote:
>
> * I still am saddled with the deep feeling that ultimate victory here 
> looks like "we don't need a val type, because by capturing the 
> nullness bit and tearability info alone we will make /enough/ usage 
> patterns always-optimizable, and we can live with the downsides". To 
> me the upsides of this simplification are enormous, so if we really 
> must reject it, I may need some help understanding why. It's been 
> stated that a non-null value type means something slightly different 
> from a non-null reference type, but I'm not convinced of this; it's 
> just that sometimes you have the technical ability to conjure a 
> "default" instance and sometimes you don't, but nullness of the type 
> means what it means either way.
>
>     * I think if we plan to go this way (.val), and then we one day
>     have a nullable types feature, some things will then be
>     permanently gross that I would hope we can avoid. For example,
>     nullness *also* demands the concept of bidirectional projection of
>     type variables, and for very overlapping reasons. This puts things
>     in a super weird place.
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/valhalla-spec-observers/attachments/20220627/2a49f911/attachment.htm>


More information about the valhalla-spec-observers mailing list