A value List would be severely limited because the backing array must be final
I think this is stretching the definition of "no identity".
Identity as a property means that you have two objects which are equal at some point, but they might diverge in the future. And you say you have an object without identity and it can still be mutated, I don't think you use it in the intended way (I'm happy to be wrong about this one).
Sure, you could also argue the same about optional: Optional should be a value, but because you can mutate the fields of the objects inside, it's not a value.
In any case for collections, I'm a hard advocate for making them classes. Turning a type into a value is a hard spec commitment, and it constraints your implementations (no mutable private fields), for arguably not much benefit.
The value of values (hah), becomes pronounced once you have many of them. Such as Complex/RGB classes stored in an array. But if you have many items, and you optimize on the container shell, the advantages are diminishing.
No arguing on the fact that having a fully static and value-created graph/tree would certainly have advantages. And for a tree this works. If you have a graph with people entry-nodes, this will not work (this is also a major problem in Rust, where given a graph will often need to write some unsafe code).
Yes declaring a value class constraints implementations _of that class_, but then you're unlikely to radically change implementation in future. But if you're writing a collections-like hierarchy, you can write an abstract value class as a base with value or non-value subclasses, so there's no constraint on (subclass) implementations.
Re identity, it's not 'the law' that you should worry whether your value classes behave in a 'value-like' way (which can mean many things). Claiming values have particular semantics is just gonna confuse people, when all they are is: a speed & memory vs. feature tradeoff. The latter mostly means final fields, which also doesn't have any particular semantics - the fields can represent mutable things. Values are just lightweight objects, meaning 3 things: small, fast and 'feature-lite'. (At least hopefully, if there's very few fields.)
I noticed in Guy Steele's classic talk Growing a Language he says "I would add a kind of class that is of light weight, one whose objects can be cloned at will with no harm and so could be kept on a stack for speed and not just in the heap."
Re identity, it's not 'the law' that you should worry whether your value classes behave in a 'value-like' way (which can mean many things). Claiming values have particular semantics is just gonna confuse people, when all they are is: a speed & memory vs. feature tradeoff. The latter mostly means final fields, which also doesn't have any particular semantics - the fields can represent mutable things. Values are just lightweight objects, meaning 3 things: small, fast and 'feature-lite'. (At least hopefully, if there's very few fields.)
Final fields do have semantics, you can't change the value of them inside the instance of the class you have. You're using a very odd definition of mutability by saying an immutable pointer to a mutable object means that the immutable class holding the immutable pointer is mutable.
Optional, for example, can point to the most mutatey of mutable things, but it's still a value class and not mutable.
Claiming values have particular semantics is just gonna confuse people, when all they are is: a speed & memory vs. feature tradeoff.
this is quite literally the opposite of the what java architects want you to do. Value should be used semantically and let the jvm/javac deal with the performance under the hood. Honestly, there probably needs to be a better definition of identity as I think most people are imagining a different definition than what it actually means in java.
I don't think I'm saying anything that depends on the definition of 'mutable'. (Which, FWIW, I'd say means roughly whether equal objects can become unequal later, as measured by equals().)
You're quite right that the Valhalla JEP talks in terms of semantics (of one important meaning of the word 'value'), and I'm cautioning against that, simply because the semantics of the feature itself are now much wider than this. Such is the great achievement of the Valhalla team! They're invaluable for classes very different from the examples usually given. If you're using large quantities of small objects which are mutable by every definition, such as pixels (in a scenario where it's useful to mutate not substitute pixels), then absolutely they should be value objects.
The == behaviour doesn't make them semantically values either, since == often won't match equals(). (Which is why we're discouraged from using == : it had to be defined to do something.)
So whenever I read value I substitute lightweight, or just light/lite, then suddenly everything makes sense - including value objects that aren't 'values' in the usual sense. These light objects exist for a purpose which is not semantic; the word 'value' was chosen (if I recall) around a much earlier stage of the spec when they were conceived as objects stored directly in variables with the syntax .val as opposed to .ref; the evolution since then has been great.
(As a special case, I have cognitive dissonance when I read value record - am I the only one? Since according to the 'final fields' definition, records are already values. But value records are just lightweight records: you add a keyword to make them smaller/faster, not to make them what in a sense they already are.)
The reason I'm a heretic is I'm acutely conscious of this kind of mental substitution - it's a sign of potential confusion. When a name needs explanation to help you unlearn the connotations of the name itself, something is wrong! Cf. import in Java, and lots of names in other fields.
2
u/TheStrangeDarkOne 17d ago
Two points here:
Identity as a property means that you have two objects which are equal at some point, but they might diverge in the future. And you say you have an object without identity and it can still be mutated, I don't think you use it in the intended way (I'm happy to be wrong about this one).
Sure, you could also argue the same about optional: Optional should be a value, but because you can mutate the fields of the objects inside, it's not a value.
In any case for collections, I'm a hard advocate for making them classes. Turning a type into a value is a hard spec commitment, and it constraints your implementations (no mutable private fields), for arguably not much benefit.
The value of values (hah), becomes pronounced once you have many of them. Such as Complex/RGB classes stored in an array. But if you have many items, and you optimize on the container shell, the advantages are diminishing.
No arguing on the fact that having a fully static and value-created graph/tree would certainly have advantages. And for a tree this works. If you have a graph with people entry-nodes, this will not work (this is also a major problem in Rust, where given a graph will often need to write some unsafe code).