Summarizing my takeaways from talking over these use cases today:

On Sep 7, 2022, at 12:56 AM, [email protected]<mailto:[email protected]> 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.

no, such classes have instances but they are empty.
By example, an array of Nothing stores just its length in memory. Accessing a 
cell return the default value, Nothing.default which is equivalent to new 
Nothing().

And it can be used in generics too, a HashMap<Integer.val, Nothing> is more or 
less a set of ints.

Clarifying: we're not talking about Nothing types, we're talking about 
Singleton types.

The main concern is that an array of Singleton.ref must have an O(n) size, 
while an array of Singleton.val *could* have an O(1) size (if this optimization 
were implemented—it's not something we've prototyped so far). In a sense, 
that's a much worse penalty for forgetting ".val" than the up to 2x storage 
penalty you might typically pay for a null flag.

Is this practical? Singleton[] is probably not something you would write 
directly, but it could make sense in a generics context (something like the 
HashMap Rémi referenced above). Not in the mainstream of our use cases, but a 
use case to keep in mind.

- wrappers/monads that modify the semantics, by example a generic value class
Atomic that plays the same role as an AtomicReference, AtomicInteger, etc
the problem here is that the default semantics is not the semantics the user
want.

Okay, so say we have value class Atomic<T>, and we're in a future where this
gets specialized. I think you're saying it will be important to say
'Atomic.val<Foo>' at all uses rather than 'Atomic<Foo>'. But I'm not clear on
the argument for why that would be. Can you elaborate?

If Atomic is declared as a ref-by-default value class, Atomic<Foo> behave as a 
ref an not as a Foo + some atomic magic.
So it is always wrong to use it.

As the author of Atomic, i don't want my users to have a way to shoot 
themselves into the foot.
Here, Atomic should be flat-by-default and also the ref variant should not 
exist, so there is no way to misuse the Atomic API.

(Point of confusion for me: no, we're not talking whether the *field* of Atomic 
is flattened. This discussion is about the treatment of the Atomic instance, 
the *wrapper* around the field.)

The argument in this case is that the class Atomic is, essentially, a 
user-defined variable modifier. (If it could be spelled 'atomic Foo x', that 
would be great, but user-defined modifiers are not a feature of Java.) It 
doesn't need to create an extra layer of wrapping around whatever it's storing; 
it just needs to add some custom behavior to reads/writes. In standard usage, 
it should never be null (and should probably always be final...).

Except... (sorry, just thinking about this now, didn't raise it earlier): how 
can a variable wrapper be a value class? The whole point is to control 
*mutation* of the payload. Value class fields are final.

So I'll tentatively say that this isn't a category of use cases Valhalla is 
going to address. You can't have mutable fields without object pointers to get 
to them.

- SIMD vectors, if those are nullable, the VM/JIT will insert implicit null
checks which are not usually a problem apart in thigh loop like users write
with SIMD vectors.

The *storage* should definitely use a value type, so in this sort of application
we'd encourage value-typed array allocations (and value-typed type arguments
for wrapping data structures).

In a loop over a flat array, I would expect it to be okay to talk about the
reference type in source, and have the JIT generate optimal code to work with
the underlying flat storage, without any allocations or null checks. My sense
is that we suspect that this can work reliably, but it could use more targeted
performance testing to confirm.

Sorry for not being clear, i'm more worry about people using IntVector instead 
of IntVector.val on stack and the VM having to emit a nullcheck inside a loop, 
by example if there is a call to a method that returns an IntVector is not 
inlined. For me, the vector API represents operation so a ref-by-default 
IntVector is a kind of useless, worst it will work but performance will suffer 
with no simple way to fix that apart taking a look to the generated assembly 
code.

In theory, there's no problem here: value class reference types can be 
scalarized everywhere on stack, and null checks/flags can often be optimized 
away. (And even if they can't—if a loop body can't be inlined and so contains 
method calls that do null checks, say—the overhead of null checks is probably 
not a significant extra cost.)

But in practice, totally possible that there are some holes. Insert open 
invitation here for people to provide performance test cases where on-stack 
usage of reference types is noticeably more expensive than the equivalent code 
using value types. That would really help to ground this conversation (is it 
something we can optimize away, or an inherent limitation? is it a serious 
problem for reference-default, or a corner case where we can encourage 
sprinkling in ".val" as a workaround?)

- existing value classes in Scala or Kotlin, those are not nullable by default
but in the current design, getClass() will happily reflect them with a nullable
class making Scala/Kotlin second class citizens of the Java platform.

Is the problem here that Scala/Kotlin will want reference-default interpretation
of names in Java source? (If so, <shrug>, if you want a good user experience
with Kotlin types, write Kotlin source.)


I don't know the answer to that question but i disagree with your conclusion, 
interrop with Java, so with the Java ecosystem is important.

Answer: no, we're not talking about Java source using Kotlin types. It's 
reflection, discussed below.

Or is the problem that they will want the reflection API to behave differently,
making Foo.val.class the "primary" class object, not a secondary one? (If so,
<shrug> again, the Java reflection API is Java-oriented, interop is not a major
factor in its design.)

Maybe I'm missing your point on this one?

The problem is that we are forcing the companion class design to other 
languages than Java.
C# has done the same mistake with their generics being too specific to C# so 
other languages on the .Net platform all suffer.

Other languages than Java can not makes Foo.val.class their primary class 
object because the companion class design is bolt into the VM.

Right: other languages might have value classes that are a good match for 
value-default—in particular they would be happiest to see the value type when 
they call 'getClass'. (Perhaps doing something else would be a behavioral 
incompatibility, should they choose to migrate their implementation from 
roll-your-own to native JVM features.)

On this one, I think we agreed that this is a "nice to have", if it works out 
in a way that is convenient for other languages, but this isn't something that 
would drive our design decisions.

Reply via email to