> Immutability is just a property of good value objects.
It sounds rather like value objects are strictly superior than immutable data structures, based on this statement. I'd love to hear an expansion of this rationale.
It's actually the other way around. If your object is immutable, whether it has identity or not (i.e. whether it's a "value object" or not) doesn't matter! There's nothing useful you could derive from that identity, so it might as well not exist.
So, languages should stop thinking in terms of "this is a reference type" and "this is a value type", and start thinking about "this references something mutable" vs "this references something immutable". For the latter, the implementation can then use by-value semantics for perf reasons, where appropriate.
Not sure what you mean. Identity comparisons are more useful for mutable objects, but even an immutable object can use one as an alternative to a unique ID. For example, you might have a queue of input events, where once an event is posted nothing about it needs to change, but multiple events with the same properties are possible (e.g. pressing the same key twice in a row).
On the other hand, mutable objects with value semantics can provide the ergonomic advantage that mutation has in some cases (e.g. 'point.y += 10' rather than 'point = Point(point.x, point.y + 10)' as well as more predictable performance, while avoiding bugs caused by accidental aliasing.
> Identity comparisons are more useful for mutable objects, but even an immutable object can use one as an alternative to a unique ID.
I think that's part of a problem. Object identity should not be used for unique IDs - when you need a unique ID, use a UniqueIdGenerator or something like that.
Java and C# and others have got this so very wrong, when they did things like, "okay, all objects have identity, let's just reuse it for other stuff", and made it possible to e.g. synchronize on arbitrary objects - synchronize(x) in Java, lock(x) in C#. Now they can't get rid of object identity, because it's part of the language semantics - even if you never synchronize on objects of your class, something else might, and making them identity-less will break that.
At the very least, let's make it explicit. When you define a class, it should be stated upfront whether it has identity or not. If it doesn't have identity, it should be immutable. If it does have identity, it still can be immutable if you want (if you need that identity for something, like your event example). But this state of affairs - immutable with identity - definitely shouldn't be normal. If it's needed, it should be requested explicitly.
I guess the real point here is that object identity has a heavier cost than it would seem from the first glance, and so it should be opt-in rather than opt-out (and it should definitely be possible to opt out).
As for `point.x += 10`, it doesn't preclude point from being immutable. It just means that the language has to desugar it into `point = Point(point.x, point.y + 10)` for you. That way, there's no accidental aliasing (since objects are still immutable - it's the reference that is mutating - so no alias can observe the change). And then the code generator, knowing that there's no aliasing, can replace it with actual in-place field update, when and where it makes sense.
How is that having value semantics? Or are you just referring to equality testing? Even then you're playing a dangerous game if you have multiple threads (one mutating, one running an equality check) and aren't using locks.
An immutable value object provides a richer contract. An immutable data structure like a tuple is going to provide a guarantee the data hasn't changed, but it doesn't provide a guarantee of what the data is.
So, languages should stop thinking in terms of "this is a reference type" and "this is a value type", and start thinking about "this references something mutable" vs "this references something immutable". For the latter, the implementation can then use by-value semantics for perf reasons, where appropriate.