Hi Rowan On Tue, Apr 2, 2024 at 10:10 PM Rowan Tommins [IMSoP] <imsop....@rwec.co.uk> wrote: > > On 02/04/2024 01:17, Ilija Tovilo wrote: > > I'd like to introduce an idea I've played around with for a couple of > weeks: Data classes, sometimes called structs in other languages (e.g. > Swift and C#). > > I'm not sure if you've considered it already, but mutating methods should > probably be constrained to be void (or maybe "mutating" could occupy the > return type slot). Otherwise, someone is bound to write this: > > $start = new Location('Here'); > $end = $start->move!('There'); > > Expecting it to mean this: > > $start = new Location('Here'); > $end = $start; > $end->move!('There'); > > When it would actually mean this: > > $start = new Location('Here'); > $start->move!('There'); > $end = $start;
I think there are some valid patterns for mutating methods with a return value. For example, Set::add() might return a bool to indicate whether the value was already present in the set. > I seem to remember when this was discussed before, the argument being made > that separating value objects completely means you have to spend time > deciding how they interact with every feature of the language. Data classes are classes with a single additional zend_class_entry.ce_flags flag. So unless customized, they behave as classes. This way, we have the option to tweak any behavior we would like, but we don't need to. Of course, this will still require an analysis of what behavior we might want to tweak. > Does the copy-on-write optimisation actually require the entire class to be > special, or could it be triggered by a mutating method on any object? To > allow direct modification of properties as well, we could move the call-site > marker slightly to a ->! operator: > > $foo->!mutate(); > $foo->!bar = 42; I suppose this is possible, but it puts the burden for figuring out what to separate onto the user. Consider this example, which would work with the current approach: $shapes[0]->position->zero!(); The left-hand-side of the mutating method call is fetched by "read+write". Essentially, this ensures that any array or data class is separated (copied if RC >1). Without such a class-wide marker, you'll need to remember to add the special syntax exactly where applicable. $shapes![0]!->position!->zero(); In this case, $shapes, $shapes[0], and $shapes[0]->position must all be separated. This seems very easy to mess up, especially since only zero() is actually known to be separating and can thus be verified at runtime. > The main drawback I can see (outside of the implementation, which I can't > comment on) is that we couldn't overload the === operator to use value > semantics. In exchange, a lot of decisions would simply be made for us: they > would just be objects, with all the same behaviour around inheritance, > serialization, and so on. Right, this would either require some other marker that switches to this mode of comparison, or operator overloading. Ilija