Value types, encapsulation, and uninitialized values

Stephen Colebourne scolebourne at joda.org
Mon Oct 15 22:49:46 UTC 2018


On Thu, 11 Oct 2018 at 15:14, Brian Goetz <brian.goetz at oracle.com> wrote:
Thanks for all the recent updates on valhalla, Looks like good progress!

> Classes guard against this through the magic of null; an instance method
> will never have to contend with a null receiver, because by the time we
> transfer control to the method, we'd already have gotten an NPE.  Values
> do not have this protection.  While there are many things for which we
> can say "users will learn", I do not think this is one of them; if a
> class has a constructor, it will be assumed that the receiver in a
> method invocation will be on an instance that has resulted from
> construction.  I do not think we can expose the programming model as-is;
> it claims to be like classes, but in this aspect is more like structs.
>
> So, some values (but not all) will want some sort of protection against
> uninitialized values.  One approach here would be to try to emulate
> null, by, say, injecting checks for the default value prior to
> dereferences.  Another would be to take the route C# did, and allow
> users to specify a no-arg constructor, which would customize the default
> value.  (Since both are opt-ins, we can educate users about the costs of
> selecting these tools, and users can get the benefits of flatness and
> density even if these have additional runtime costs.)  The latter route
> is less rich, but probably workable.  Both eliminate the (likely
> perennial) surprise over uninitialized values for zero-sensitive classes.

In general, I don't like nulls. But I think a null-like approach might
well be the best answer here.

I've mentioned before that not all value types are the same. Thus
there are two clear groups of value type:

- NonNull values - Complex, Point, LongLong , Optional - where there
is a sensible default (zero) value
- Nullable values - LocalDate, Currency, Money - where there is no
good default value

Given the split, and that LocalDate already has null as a default
without the world collapsing, this really does seem like a good choice
for value type authors to make (and may make migration
easier/clearer). Forcing a default value on classes like Money or
LocalDate (even if via a constructor) is forcing a logically nonsense
value to be handled - not very "codes like a class". So I just can't
see how that option would work. Null already is the concept for
default "not initialized", so should be the conceptual model to build
on here.

If Money is a Nullable value type we have something at the Java level
like (assuming emotional types):

  Money a = Money.of(...);  // fine, local variable is initialized
  Money? b = null;  // fine, local variable is not initialized
  Money c = null;  // not fine as not initialized

  if (b == null) { ...} // fine, and the clearest way to test for the
not initialized (default) state

  Money[] = new Money[6];  // not fine as not initialized
  Money[] = new Money[6].populate(index -> ...);  // fine with some
kind of populator syntax

Note that the developer writing Money doesn't have to worry about the
null-like state much. They have to do two things:
- set a syntax flag to permit the value type to be nullable
- ensure that the all-bits zero state of the type has no conceptual
meaning (easily true for Money or LocalDate for example)

Maybe there is a way that the author could opt-in to accept that
all-bits zero has some meaning, ie. effectively making
Optional.empty() == null . This would be nice :-)

But what about the JVM level? Maybe the variable starts as LMoney and
becomes QMoney when first initialized? Naively, it seems that it
wouldn't be too hard to know whether the variable is null or not with
Q/L tracking.

What I'm keen to avoid is a situation where NonNull values have a much
better performance model that Nullable ones. ie. a Nullable value type
should still be able to gain the benefits of flattening, otherwise
whats the point? (A nullable value type is just a value type where the
bit-pattern of zero is given a special name and prevented from being
invoked on. Neither of those things prevent the value from being
flattened.)

thanks
Stephen


More information about the valhalla-dev mailing list