> On Feb 3, 2021, at 9:14 AM, Nikita Popov <nikita....@gmail.com> wrote:
> 
> On Mon, Dec 28, 2020 at 9:24 PM Larry Garfield <la...@garfieldtech.com>
> wrote:
> 
>> There's been a number of discussions of late around property visibility
>> and how to make objects more immutable.  Since it seems to have been
>> well-received in the past, I decided to do a complete analysis and context
>> of the various things that have been floated about recently.
>> 
>> The full writeup is here:
>> 
>> https://peakd.com/hive-168588/@crell/object-properties-and-immutability
>> 
>> I hope it proves stimulating, at least of discussion and not naps.
>> 
> 
> Thanks for the analysis Larry! I want to add a couple of thoughts from my
> side.
> 
> First of all, I think it's pretty clear that "asymmetric visibility" is the
> approach that gives us most of what we want for the least amount of effort.
> Asymmetric visibility has clear semantics, is (presumably) trivial to
> implement, and gives immutability guarantees that are "good enough" for
> most practical purposes. It's the pragmatic choice, and PHP is all about
> pragmatism...
> 
> That said, I don't think that asymmetric visibility is the correct solution
> to this problem space -- I don't think asymmetric visibility is ever (or
> only very rarely) what we actually want, it's just a good enough
> approximation. Unfortunately, the alternatives are more complex, and we
> have a limited budget on complexity.
> 
> Here are the pieces that I think would make up a proper solution to this
> space:
> 
> 1. initonly properties. This is in the sense of the previous "write once
> properties" proposal, though initonly is certainly the better name for the
> concept. Initonly properties represent complete immutability both inside
> and outside the class, and I do believe that this is the most common form
> of immutability needed (if it is needed at all).
> 
> Of course, as you correctly point out, initonly properties are incompatible
> with wither patterns that rely on clone-then-modify implementations. I
> think that ultimately, the "wither pattern" is an artifact of the fact that
> PHP only supports objects with by-handle semantics. The "wither pattern"
> emulates objects with by-value semantics, in a way that is verbose and
> inefficient.
> 
> I do want to point out that your presentation of copy-on-write when it
> comes to withers is not entirely correct: When you clone an object, this
> will always result in a full copy of the object, including all its
> properties. If you call a sequence of 5 wither methods, then this will
> create five objects and perform a copy of all properties every time. There
> is really no copy-on-write involved here, apart from the fact that property
> values (though not the property storage) can still be shared.
> 
> 2. This brings us to: Objects with by-value semantics. This was discussed
> in the thread, but I felt like it was dismissed a bit prematurely.
> 
> Ultimately, by-value semantics for objects is what withers are emulating.
> PSR-7 isn't "immutable", it's "mutable by-value". "Immutable + withers" is
> just a clumsy way to emulate that. If by-value objects were supported, then
> there would be no need for wither methods, and the "clone-then-modify"
> incompatibility of initonce properties would not be a problem in practice.
> You just write $request->method = 'POST' and this will either efficiently
> modify the request in-place (if you own it) or clone it and then modify it
> (if it is shared).
> 
> Another area where by-value objects are useful are data structures. PHP's
> by-value array type is probably one of those few instances where PHP got
> something right in a major way, that many other languages got wrong. But
> arrays have their own issues, in particular in how they try to service both
> lists and dictionaries at the same time, and fail where those intersect
> (dictionaries with integer keys or numeric string keys). People regularly
> suggest that we should be adding dedicated vector and dictionary objects,
> and one of the issues with that is that the resulting objects would follow
> the usual by-handle semantics, and would not serve as a mostly drop-in
> replacement for arrays. It is notable that while HHVM/Hack initially had
> vec and dict object types, they later created dedicated by-value types for
> these instead.
> 
> 3. Property accessors, or specifically for your PSR-7 examples, guards. The
> __clone related issues you're mostly dealing with in your examples are
> there because you need to replicate the validation logic in multiple
> places. If instead you could write something like
> 
>    public string $method {
>        guard($version) {
>            if (!in_array($version, ['1.1', '1.0', '2.0'])) throw new
> InvalidArgumentException;
>        }
>    }
> 
> then this would ensure consistent enforcement of the property invariants
> regardless of how it is set.
> 
> Circling back, while I think that a combination of these features would be
> the "proper" solution to the problem, they also add quite a bit of
> complexity. Despite what I say above, I'm very much not convinced that
> adding support for by-value objects is a good idea, due to the confusion
> that two different object semantics could cause, especially if writing
> operations on them are not syntactically distinct.
> 
> I've written up an initial draft for property accessors at
> https://wiki.php.net/rfc/property_accessors, but once again I get the
> distinct impression that this is adding a lot of language complexity, that
> is possibly not justified (and it will be more complex once inheritance is
> fully considered).

I wish PHP had property accessors like this a decade ago.

Question/Comments:

1. Interfaces: Would it be possible to define in an interface that a property 
is readable but cannot be writable?  Your example seems to me to imply not.

2. Traits: As they are not mentioned, does that mean property accessors would 
implicitly be supported or explicitly not supporters?  If the latter, why not?

3. isset(): Why does isset() throw an Error on write-only property? Why 
shouldn't it just return true, or return !isnull($this->{backing_prop})?

4. get_object_vars()/get_class_vars(): These are used often when looping 
through properties to access or assign as they are much more convenient (and 
probably faster?) than using Reflection but with get-only and set-only they 
would become rather useless unless a developer could specify to return only 
get-only or only set-only properties.  Can we also add an optional parameter to 
both functions to allow passing a flag, something like VARS_GET_ONLY and 
VARS_SET_ONLY?  

5. Write-only properties: StackOverflow has a question[1] with several answers 
that give some good reasons for write-only properties, not the least of which 
is DI via properties vs. constructors.

6. Basic accessors: In the 4th example class Test in the "Basic accessors" 
section the get() uses a $value variable. Should this be $this->value, or is 
$value come from somewhere else?

7. var: +1 for requiring visibility specified on all assessors if any 
visibility is specified.

8. Backing properties: Can you consider making backing properties to always 
have the same name as the property but with a leading underscore?  This would:

a. Allow them to be easily recognized when scanning code, 
b. (Maybe?) allow on generating them when you see an underscore property in 
code, 
c. Allow internally accessing the raw backing property internally vs the 
accessed code, and 
d. Having both $prop and $_prop could help while debugging

9. lazy(): +1 for init() independent of get(), instead of lazy()

10. set() vs guard: Why does set() have an explicitly declared $value parameter 
but guard does not? Shouldn't that be consistent? I'd drop it from set() to 
reduce verbosity, but adding to guard() would work too.

11. get() and guard: Explicitly there should be no interaction (right?)

12. set() and guard: Explicitly first guard is called and if successful set() 
is called, otherwise set() is not called (right?)

13. Illegal constructor promotion: Seems an unfortunate limitation. Can you 
provide an example in the RFC of "embedding of very large property declarations 
in the constructor signature" that would be considered problematic?

14. Parent accessors: What am I missing here?  Why would "parent:$prop" be 
unavailable/problematic?

15. Incompatible inheritance: It seems "by-value" array properties are the only 
problem? Can overriding explicitly declared arrays with accessor properties be 
disallowed but everything else allowed? I know it would be inconsistent and 
unfortunate, but I question whether an array as a property should not be better 
encapsulated anyway and thus be a reason an educated developer would avoid 
array properties in parent classes except for use-cases where accessor property 
would likely never be needed?

Hope this list helps.

-Mike

[1] 
https://stackoverflow.com/questions/2213879/do-write-only-properties-have-practical-applications

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php

Reply via email to