Nullness markers to enable flattening

Kevin Bourrillion kevinb at google.com
Wed Feb 8 20:43:48 UTC 2023


On Wed, Feb 8, 2023 at 7:16 AM Brian Goetz <brian.goetz at oracle.com> wrote:

"Sensible defaults" has been a long-standing challenge.  The VM has a
> very, very strong bias towards the default state of memory being all
> zeroes.  It is convenient that this yields sensible defaults for our
> primitives (numeric zero) and references (null pointers). Nullable types
> have a good zero default: null.  "Well behaved" value types like Complex
> have a good zero default: zero.


To get my stance (that you already know) onto the list: for a nullable
type, null is immeasurably *better* than just a "sensible default"; it is
*no default at all*. Information is simply missing, never guessed at. Null
is a wonderful thing (it's only a null-oblivious type system that makes us
think it isn't).

Whatever the all-zeroes value is worth for value types is much less
clear-cut. (hey, if zero is such a great default, why don't we initialize
locals to it too?)

A type that models (some approximation of) an algebraic ring is the Very
Special Best Use Case. It is blessed to have two clearly-most-common
reduction operations, and a single value (zero) that both serves as the
identity for one of those operations, and is utterly destructive to the
other (i.e., multiplying onto a value without initializing it to 1 first
will at least make your bug clearly noticeable!). I feel these discussions
over-rotate toward those special cases. Those numeric use cases might be
the most *motivating* ones, but that doesn't mean they're necessarily the
most common or the most "important" (for some definition).



> That is: if your zero default is bad, you need null as a default. This
> is one of the central reasons for having Bucket 2.  (Spoiler: I don't
> like bucket 2.)
>

And, of course, I (heart) bucket 2. :-) We (Google) expect to send out
migration changelists throughout our internal codebase to make every bucket
1 class into a bucket 2 class that we can (mainly not the mutable ones,
which our codebase is biased against anyway). So I guess we have a gap here!

Refresher for observers: "Bucket 1" is what every class and class-based
type is today. Bucket 2 is the same exact thing but without identity.
Without identity, you have a number of restrictions -- no non-final fields,
no locking, etc. Of course, a whole ton of classes never wanted those
things in the first place. Bucket 3 used to refer to an "inlinable" value
type in the vein of `int`, but after all the latest changes that have been
discussed, it now looks to me like B2 vs. B3 is a decision for the
*runtime* to make, not me. (And that's outstanding!)

But as regards B1 vs B2 I think it's as pure a case of a backward default
as any. If you want identity, of course you can have it. If you want your
field to be reassignable or your nested class to have a hidden reference to
an instance of the enclosing class, you can have these things. But every
reasonable compendium of best-practice advice for Java will tell you to
make nested classes static and fields final until you need otherwise. Never
let your desire for brevity outweigh the more important considerations. Why
would this case be different, I have to wonder?

Using B2 by default is both principled and practical. It's how we *defer to
the runtime* to make performance decisions for us. And we really really
like doing that.

And none of this has anything to do with being a special kind of class like
`Complex`.

--
Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/valhalla-spec-observers/attachments/20230208/ff3237f2/attachment-0001.htm>


More information about the valhalla-spec-observers mailing list