Hi Rowan On Fri, Apr 5, 2024 at 12:28 AM Rowan Tommins [IMSoP] <imsop....@rwec.co.uk> wrote: > > On 03/04/2024 00:01, Ilija Tovilo wrote: > > Regardless of the implementation, there are a lot of interactions we will > want to consider; and we will have to keep considering new ones as we add to > the language. For instance, the Property Hooks RFC would probably have needed > a section on "Interaction with Data Classes".
That remark was implying that data classes really are just classes with some additional tweaks. That gives us the ability to handle them differently when desired. However, they will otherwise behave just like classes, which makes it not so different from your suggestion. > On a practical note, a few things I've already thought of to consider: > > - Can a data class have readonly properties (or be marked "readonly data > class")? If so, how will they behave? Yes. The CoW semantics become irrelevant, given that nothing may trigger a separation. However, data classes also include value equality, and hashing in the future. These may still be useful for immutable data. > - Can you explicitly use the "clone" keyword with an instance of a data > class? Does it make any difference? Manual cloning is not useful, but it's also not harmful. So I'm leaning towards allowing this. This way, data classes may be handled generically, along with other non-data classes. > - Tied into that: can you implement __clone(), and when will it be called? Yes. `__clone` will be called when the object is separated, as you would expect. > - If you implement __set(), will copy-on-write be triggered before it's > called? Yes. Separation happens as part of the property fetching, rather than the assignment itself. Hence, for `$foo->bar->baz = 'baz';`, once `Bar::__set('baz', 'baz')` is called, `$foo` and `$foo->bar` will already have been separated. > - Can you implement __destruct()? Will it ever be called? Yes. As with any other object, this will be called once the last reference to the object goes away. There's nothing special going on. It's worth noting that CoW makes `__clone` and `__destruct` somewhat nondeterministic, or at least non-obvious. > > Consider this example, which would > work with the current approach: > > > > $shapes[0]->position->zero!(); > > I find this concise example confusing, and I think there's a few things to > unpack here... I think you're putting too much focus on CoW. CoW should really be considered an implementation detail. It's not _fully_ transparent, given that it is observable through `__clone` and `__destruct` as mentioned above. But it is _mostly_ transparent. Conceptually, the copy happens not when the method is called, but when the variable is assigned. For your example: ```php $shape = new Shape(new Position(42,42)); $copy = $shape; // Conceptually, a recursive copy happens here. $copy->position->zero!(); // $shape is already detached from $copy. The ! merely indicates that the value is modified. ``` > The array access doesn't need any special marker, because there's no > ambiguity. This is only true if you ignore ArrayAccess. `$foo['bar']` does not necessarily indicate that `$foo` is an array. If it were a `Vector`, then we would absolutely need an indication to separate it. It's true that `$foo->bar` currently indicates that `$foo` is a reference type. This assumption would break with this RFC, but that's also kind of the whole point. > What is going to be CoW cloned, and what is going to be modified in place? I > can't actually know without knowing the definition behind both $item and > $item->shape. It might even vary depending on input. For the most part, data classes should consist of other value types, or immutable reference types (e.g. DateTimeImmutable). This actually makes the rules quite simple: If you assign a value type, the entire data structure is copied recursively. The fact that PHP delays this step for performance is unimportant. The fact that immutable reference types aren't cloned is also unimportant, given that they don't change. Ilija