Perhaps it’s time to revisit my old attempt at formalizing value semantics through purity. What I found out is that value semantics is something a function decides to preserve, or break, depending on what it accesses. So you end up having to classify functions into value-semantics-preserving ones, called pure in the above, and others which are not preserving value semantics.
If you attach value semantics to a type rather than a function, adding an extension could breaks value semantics, or subclassing a class could breaks value semantics. Even without extensions: Array breaks value semantics by exposing its capacity, and a classes breaks it by making its address observable and comparable (you can’t clone it without making it different at the address level).
And then it depends on the application: if you sort class references but only look at the address which you treat as opaque (never dereferencing the pointer), then you have value semantics: the value are the addresses, even though they can lead you to some mutable state elsewhere. If you look only at the characters in an immutable NSString without looking at its address, you’re preserving value semantics too, your just looking at it at a different kind of value it carries. There’s a duality here: the same NSString contains two kinds of values that when mixed appears to break value semantics. This applies to Array too if you look at its capacity field.
There is a subset of value semantics that is useful for concurrency which consists of determining whether we’re accessing shared state. I’d suggest we focus on that instead, as it is much less subjective and does not have superimposed meanings depending on how you look at the type in a particular context.