What we have lost ?

John Rose john.r.rose at oracle.com
Sat Sep 10 01:50:34 UTC 2022


On 7 Sep 2022, at 19:19, Dan Smith wrote:

> Summarizing my takeaways from talking over these use cases today:
>
> On Sep 7, 2022, at 12:56 AM, 
> forax at univ-mlv.fr<mailto:forax at univ-mlv.fr> wrote:
>
> Here is a list of such value types:
> - unit types, value types like by example Nothing (which mean that a 
> method
> never returns) with no fields.
> Because creating a ref on it creates something :)
>
> If you truly mean for such a class to have no instances, that's not 
> something a
> class with a value type can assert—the default instance always 
> exists. I can
> see how it would be nice, for example, to have a type like Void that 
> is also
> non-nullable, but value types are not the feature to accomplish that.

To pile on with Dan, value types are not at all like empty types 
(nothing, not-reached).  Like all objects, they are records, products of 
fields, and so can be units (what Dan calls singletons), but not empties 
(nothings).  Only types structured as sums can be empty.

Background: A unit type is different from an empty type, just as the 
product identity element 1 (in various settings) differs from the sum 
identity element 0 (in various settings).  A unit type has one instance 
and takes up no storage (O(1) storage globally for its metadata), but 
can be stored in many places.  An empty type has zero instances, and 
takes up no storage because it cannot be stored anywhere, not even on 
stack.

Void is a unit type, because you can return from a void method.  
NotReached is an empty type, because you cannot return from a method 
that declares its return as not reached.

If you add a unit type to a union (sum type), you increase the 
information content of that union; a unit type is not an identity for 
sum-aggregation.  By contrast, if you add an empty type to a union, you 
leave the union unchanged (since that union branch is never reached); an 
empty type is an identity for sum-aggregation in the same way that 0 is 
an identity for addition.

Likewise, if you add a unit type to a product (tuple record), you leave 
the record unchanged in its information content, because a unit type is 
an identity for product-aggregation.  But if you add an empty type to a 
record, you change it; the record can no longer be instantiated, so it 
collapses to an empty type (with a different label, maybe).  This is one 
reason we associate 1 with units and 0 with “empties”:  the product 
of an empty times any type is empty, just as 0*x = 0.

Taking this all into account, a type `Map<int,Nothing>` is *not* a set 
of ints.  It is a set that *you cannot take any ints out of*.  It is an 
empty type, isomorphic to `Nothing` itself (assuming `Nothing` is empty 
not a unit).

I think the closest Java gets to mapping the world of empty results is 
`throws` clauses, which talk about how a method’s (normal) return 
point can fail to be reached.  (From another POV, both kinds of method 
returns, normal and exceptional, are coordinated terms in a 
sum-aggregation of the form `Either<NormalType,ThrowType>`.)  But 
`throws` don’t get all the way there, since regardless of its `throws` 
clauses, a method can *always* return normally, so it’s return type is 
never actually empty.

An empty type is what you get when you dereference `null`.  Maybe the 
type of dereferencing `null` could be something like 
`NotReached<NullPointerException>`?  Maybe.  But if we did such a thing, 
the generic type `NotReached` would have to be special-cased everywhere 
in the language.  Probably you could never declare a field or variable 
of that type.  (Except in dead code.  But Java forbids you to write dead 
code.)  Methods which return `NotReached` would be obligated to throw 
the indicated exception (or `RTE` or `Error`) instead of returning.

FWIW, I really like that value types can express labeled units, and 
embrace all the quirky corollaries, such as that an array of those guys 
is isomorphic to a non-negative `int`.  I look forward to the day when 
`Map.Entry<T,Void>` specializes so that the second component takes up 
zero bits, and so sets can be built on maps with storage efficiency.

*Note* that `Void`!  I really mean `java.lang.Void`, not a new value 
class.  If I specialize to a type which is hardwired to have zero 
instances (and that means it is always `null`) I have a unit type 
that’s even better than a value-class with no fields, since the `null` 
is the only possible value, not `null` plus a default instance.  
Specialized generics should IMO specialize on `Void`, when we get there!

The conversation about a hypothetical specialized generic called 
`Atomic` for Java is way premature, IMO.  Is it mutable or immutable?  
Does it affect its container?  …Who knows?  I am sympathetic with the 
idea that wrapping a specialized generic around a random type can 
somehow create a new kind of container for that type; I envision such 
generics for `Atomic`, `Contended`, `WeakReference`, `Lazy`, and many 
more, to replace various ad hoc mechanisms we have today.  I want to try 
for it, but I don’t regard any of those as a necessary goal, just a 
“nice to have if we can get it”.

(One example:  We suffer from the lack, today, of arrays of weak 
references.  But it’s not clear that value-types-plus-arrays can ever 
help with that.  I’m OK if they don’t.  We don’t need to replicate 
all of C++’s `atomic` template tricks, nor the tricks from any 
particular language.)

Regarding unwanted nulls from ref-default types:  Meh; the JVM has a 
quarter century invested in dealing with unwanted nulls, to the point 
where folks just don’t think about them very much as a performance 
problem.  As a correctness problem, they are a pain, but again we have 
many ways to cope.  I think forgetting to put `.val` on your fields or 
array creations is just fine; you can ship code that has too many nulls 
with a clear conscience, as long as you have a way to recognize when 
they are a measurable problem.  At that point, fix your code by adding 
`.val` in a few places.  But you can wait to measure a need in most 
cases.  People writing high-performance code with complex numbers or 
vectors will readily learn where they must “salute the val”; it is 
no worse than the current question of when to use `float` or `Float`.  
The rest of us can mostly ignore the issue.

To conclude, I don’t feel much “loss” here, nor do I think we are 
“lost”.  But some of the confusion between units and empties did 
leave me “at a loss”.  Hence this message.

— John
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/valhalla-spec-observers/attachments/20220909/ec63e86e/attachment.htm>


More information about the valhalla-spec-observers mailing list