Nullity (was: User model stacking: current status)
Kevin Bourrillion
kevinb at google.com
Thu May 12 01:45:23 UTC 2022
On Mon, May 9, 2022 at 2:14 PM Brian Goetz <brian.goetz at oracle.com> wrote:
Now, let's step out onto some controversial territory: how do we spell .ref
> and .val? Specifically, how good a fit is `!` and `?` (henceforth,
> emotional types), now that the B3 election is _solely_ about the
> existence of a zero-default .val? (Before, it was a poor fit, but now it
> might be credible. Yet another reason I wanted to tease apart what
> "primitive" meant into independent axes.)
>
I'm certainly open to the possibility that `?` or `!` can help us out here.
But the only way it can fly is if it is clearly a stepping stone toward a
proper nullable-types feature. We would not want to get stuck here.
That unfortunately forces us to have some clear idea how we would
want/expect such a feature to look and work.
My goal here is not to dive into the details of "let's design nullable
> types", as that would be a distraction at this point,
>
(out of order reply) Well... I'm sorry for what follows, then. I think
there is no way to know whether current proposals would be painting
ourselves into a corner unless we explore the topic a bit.
Here is my current concept of this beast:
* bare `String` means what it always has ("String of ambiguous nullness")
* `String!` indicates "an actual string" (I don't like to say "a non-null
string" because *null is not a string!*)
* `String?` indicates "a string or null".
* `!` and `?` also work to project a type variable in either direction.
* Exclamation fatigue would be very real, so assume there is some way to
make `!` the default for some scope
* javac (possibly behind a flag) would need to treat `?` and a
suitably-blessed `@Nullable` identically, and same for `!` and `@NonNull`;
there is just no way to survive a transition otherwise
Enter Valhalla:
* (Let's say we have B1, B2a/B3a (atomic), and B2b/B3b ("b"reakable?))
* On a B3 value type like `int`, `?` would be nonsense and `!` redundant.
* That's equally true of a B3 value type spelled `Complex.val` (if such a
thing exists).
* (assuming `Complex` is ref-default) all three of `Complex`, `Complex?`,
and `Complex!` have valid and distinct meanings.
Now, imagining that we reached this point... would B3a/B3b (as a
language-level thing) become immediately vestigial?. With Complex as a B2a
or B2b, would `Complex!` ever not optimize to the B3-like *implementation*?
I think the (standard) primitives could be understood as B2 themselves,
with `int` just being an alias for `Integer!`.
Obviously, if it would become vestigial, then we should try to avoid ever
having it all, by simply :-) delaying it and solving B2-then-nullness.
Pro: users think they really want emotional types.
>
Quibble: nah, we *know* we want them...
> Con: These will surely not initially be the full emotional types users
> think they want, and so may well be met by "you idiots, these are not the
> emotional types we want"
>
We don't have to worry about this if we have a good story that it's a
stepping stone. The stepping stone could be that it just doesn't work for
B1 types yet. I would say that there's a moral hazard that people might
choose B2 just to get that... but since that only happens if they don't
*need* identity... we'd like them to do that anyway!
> Con: To the extent full emotional types do not align clearly with
> primitive type projections, we might be painted into a corner and it might
> be harder to do emotional types.
>
I'm questioning whether we would need primitive type projections at all,
just nullable/non-null type projections.
> Risk: the language treatment of emotional types is one thing, but the real
> cost in introducing them into the language is annotating the libraries.
> Having them in the language but not annotating the libraries on a timely
> basis may well be a step backwards.
>
For a while we'd only have to annotate as we migrate B1 -> B2.
And it can be automated to a significant degree, more than halfway I think.
If we had full emotional types, some would have their non-nullity erased
> (`String!` erases to the same type descriptor as ordinary `String`) and
> some would have it reified (Integer! translates to a separate type, the `I`
> carrier.) This means that migrating `String` to `String` might be
> binary-compatible, but `Integer` to `Integer!` would not be. (This is
> probably an acceptable asymmetry.)
>
Agree acceptable.
> But a bigger question is whether an erased `String!` should be backed up
> by a synthetic null check at the boundary between checked and unchecked
> code, such as method entry points (just as unpacking a T from a generic is
> backed up by a synthetic cast at the boundary between generic and explicit
> code.) This is reasonable (and cheap enough), but may be on a collision
> course with some interpretations of `String!`.
>
There seem to be a continuum of approaches from "more checking/less
pollution" to "more pollution/problems get found far from where they really
happened." The generics experience was that few people bothered to use
`checkedCollection()`, and I doubt many added type checks via bytecode
either, and it all worked well enough, buuut there are a few reasons for
that that don't translate to null.
--
Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com
More information about the valhalla-spec-observers
mailing list