[RFC] Readonly properties

  114729
June 4, 2021 15:19 nikita.ppv@gmail.com (Nikita Popov)
Hi internals,

I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2

This proposal is similar to the
https://wiki.php.net/rfc/write_once_properties RFC that has been declined
previously. One significant difference is that the new RFC limits the scope
of initializing assignments. I think a key mistake of the previous RFC was
the confusing "write-once" framing, which is both technically correct and
quite irrelevant.

Please see the rationale section (
https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this
proposal relates to other RFCs and alternatives.

Regards,
Nikita
  114730
June 4, 2021 15:34 pierre-php@processus.org (Pierre)
Le 04/06/2021 à 17:19, Nikita Popov a écrit :
> Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2 > > This proposal is similar to the > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section ( > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives. > > Regards, > Nikita > Hello,
I like this RFC, but I have a question: > Specifying an explicit default value on readonly properties is also not allowed: Does that mean I wouldn't be allowed to write something such as: Because the readonly+promoted+named combo is very powerful but if you can't assign defaults it makes it much less interesting. Regards, -- Pierre
  114731
June 4, 2021 15:35 pierre-php@processus.org (Pierre)
Le 04/06/2021 à 17:34, Pierre a écrit :
> Le 04/06/2021 à 17:19, Nikita Popov a écrit : >> Hi internals, >> >> I'd like to open the discussion on readonly properties: >> https://wiki.php.net/rfc/readonly_properties_v2 >> >> This proposal is similar to the >> https://wiki.php.net/rfc/write_once_properties RFC that has been >> declined >> previously. One significant difference is that the new RFC limits the >> scope >> of initializing assignments. I think a key mistake of the previous >> RFC was >> the confusing "write-once" framing, which is both technically correct >> and >> quite irrelevant. >> >> Please see the rationale section ( >> https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this >> proposal relates to other RFCs and alternatives. >> >> Regards, >> Nikita >> > Hello, > > I like this RFC, but I have a question: > > > Specifying an explicit default value on readonly properties is also > not allowed: > > Does that mean I wouldn't be allowed to write something such as: > > > class Point { > >     public function __construct( > >         public readonly int $x = 0, > >         public readonly int $y = 0, > >     ) {} > > } > > > $foo = new Point(x: 12); > > ?> > > Because the readonly+promoted+named combo is very powerful but if you > can't assign defaults it makes it much less interesting. > > Regards, > > -- > > Pierre > Please ignore my mail, the answer is just below...
Sorry for the noise. -- Pierre
  114732
June 4, 2021 18:43 matthewmatthew@gmail.com (Matthew Brown)
This is a great idea!

Might be worth mentioning that Psalm already supports a `@readonly`
docblock annotation (first suggested by Nuno Maduro), and it matches the
proposed behaviour (though Psalm doesn't currently prevent inheritance
issues):

Example: https://psalm.dev/r/7ed5872738

On Fri, 4 Jun 2021 at 11:19, Nikita Popov ppv@gmail.com> wrote:

> Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2 > > This proposal is similar to the > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section ( > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives. > > Regards, > Nikita >
  114733
June 5, 2021 08:39 nicolas.grekas+php@gmail.com (Nicolas Grekas)
Hi Nikita,

Le ven. 4 juin 2021 à 17:19, Nikita Popov ppv@gmail.com> a écrit :

> Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2 >
Thanks for the proposal, that's quite appealing! I voted "no" on the previous proposal because it didn't play well with cloning. That's why I favoured asymmetric visibility. I see that this new iteration has the same issue, so I'd like to know: What about putting readonly properties in the uninitialized state when cloning objects? Cheers, Nicolas
  114734
June 5, 2021 09:47 pierre-php@processus.org (Pierre)
Le 04/06/2021 à 17:19, Nikita Popov a écrit :
> Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2 > > This proposal is similar to the > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section ( > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives. > > Regards, > Nikita > Hello,
If I understood it well you count assignments, one note thought after re-reading once again, you state:
> This variant is not allowed, as the initializing assignment occurs from outside the class:
Does this mean it'll prevent programmatic object hydration using the scope-stealing closure pattern ? Such as: newInstanceWithoutConstructor(); (\Closure::bind(     function (SomeClass $object) ($bar         $object->foo = $bar;     },     null,     SomeClass::class ))($object); ?> I know this is a weird stuff to do I wouldn't advise to others myself, but some hydration libraries such as ocramius/generated-hydrator do leverage this mechanism to hydrate objects, and I sometime do it myself in business code, sporadically, to hydrate objects from database without needing to define a constructor for those value objects. If it does prevent such code, will there be some additional mechanism to create value objects with readonly properties without constructor ? Such as an JavaScript object-like object instanciation mechanism ? I know this goes far outside of this RFC's own scope, but maybe this is the occasion to open a discussion about this, this would play well with readonly properties and various serializers or hydrators libraries. Regards, -- Pierre
  114735
June 5, 2021 09:53 deleugyn@gmail.com (Deleu)
On Sat, Jun 5, 2021, 11:47 Pierre <pierre-php@processus.org> wrote:

> Le 04/06/2021 à 17:19, Nikita Popov a écrit : > > Hi internals, > > > > I'd like to open the discussion on readonly properties: > > https://wiki.php.net/rfc/readonly_properties_v2 > > > > This proposal is similar to the > > https://wiki.php.net/rfc/write_once_properties RFC that has been > declined > > previously. One significant difference is that the new RFC limits the > scope > > of initializing assignments. I think a key mistake of the previous RFC > was > > the confusing "write-once" framing, which is both technically correct and > > quite irrelevant. > > > > Please see the rationale section ( > > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > > proposal relates to other RFCs and alternatives. > > > > Regards, > > Nikita > > > Hello, > > If I understood it well you count assignments, one note thought after > re-reading once again, you state: > > > This variant is not allowed, as the initializing assignment occurs from > outside the class: > > Does this mean it'll prevent programmatic object hydration using the > scope-stealing closure pattern ? Such as: > > > class SomeClass > { > public readonly int $foo; > } > > $bar = 12; > > > $object = (new > \ReflectionClass('SomeClass'))->newInstanceWithoutConstructor(); > > > (\Closure::bind( > function (SomeClass $object) ($bar > $object->foo = $bar; > }, > null, > SomeClass::class > ))($object); > > ?> > > I know this is a weird stuff to do I wouldn't advise to others myself, > but some hydration libraries such as ocramius/generated-hydrator do > leverage this mechanism to hydrate objects, and I sometime do it myself > in business code, sporadically, to hydrate objects from database without > needing to define a constructor for those value objects. > > If it does prevent such code, will there be some additional mechanism to > create value objects with readonly properties without constructor ? Such > as an JavaScript object-like object instanciation mechanism ? > > > class SomeClass > { > public readonly int $foo; > } > > $bar = 12; > > $object = { > foo: $bar, > }; > > ?> > > I know this goes far outside of this RFC's own scope, but maybe this is > the occasion to open a discussion about this, this would play well with > readonly properties and various serializers or hydrators libraries. > > Regards, > > -- > > Pierre >
Sounds like a revelant subject to be raised, indeed. But I honestly hope that this awkward hack will not prevent us from getting read-only attributes.
>
  114736
June 5, 2021 10:06 nikita.ppv@gmail.com (Nikita Popov)
On Sat, Jun 5, 2021 at 11:47 AM Pierre <pierre-php@processus.org> wrote:

> Le 04/06/2021 à 17:19, Nikita Popov a écrit : > > Hi internals, > > I'd like to open the discussion on readonly properties:https://wiki.php.net/rfc/readonly_properties_v2 > > This proposal is similar to thehttps://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section (https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives. > > Regards, > Nikita > > > Hello, > > If I understood it well you count assignments, one note thought after > re-reading once again, you state: > > > This variant is not allowed, as the initializing assignment occurs from outside the class: > > Does this mean it'll prevent programmatic object hydration using the > scope-stealing closure pattern ? Such as: > > > class SomeClass > { > public readonly int $foo; > } > > $bar = 12; > > > $object = (new \ReflectionClass('SomeClass'))->newInstanceWithoutConstructor(); > > > (\Closure::bind( > function (SomeClass $object) ($bar > $object->foo = $bar; > }, > null, > SomeClass::class > ))($object); > > ?> > > I know this is a weird stuff to do I wouldn't advise to others myself, but > some hydration libraries such as ocramius/generated-hydrator do leverage > this mechanism to hydrate objects, and I sometime do it myself in business > code, sporadically, to hydrate objects from database without needing to > define a constructor for those value objects. > Yes, this is supported. What matters is the scope from which the
initialization occurs. If you rebind a closure to the scope of the class, then you can perform any operations that would be legal inside that class, including access to private properties and initialization of readonly properties. Regards, Nikita
  114737
June 5, 2021 10:17 pierre-php@processus.org (Pierre)
Le 05/06/2021 à 12:06, Nikita Popov a écrit :
> Yes, this is supported. What matters is the scope from which the > initialization occurs. If you rebind a closure to the scope of the class, > then you can perform any operations that would be legal inside that class, > including access to private properties and initialization of readonly > properties. > > Regards, > Nikita
OK, I wasn't sure I thought I'd ask, this is nice, thank you ! I can't vote, but I love this RFC, thank you very much for this. I sincerely hope this will not get into flames. -- Pierre
  114738
June 5, 2021 14:28 kontakt@beberlei.de (Benjamin Eberlei)
On Fri, Jun 4, 2021 at 5:20 PM Nikita Popov ppv@gmail.com> wrote:

> Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2 > > This proposal is similar to the > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section ( > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives. >
I have a question about Reflection support, the RFC states:
> ReflectionProperty::setValue() can bypass the requirement that initialization occurs from the scope where the property has been declared.
However, reflection cannot modify a readonly property that has already been initialized. Is there a reason why this is not possible? I am thinking about ORMs or Deserializers here where a pattern would be: class MyDataObject { public function __construct( public readonly $foo ) {} } $dataObject = $reflectionClass->newInstanceWithoutConstructor(); $dataObject->getProperty('foo')->setValue($row['value']);
> > Regards, > Nikita >
  114740
June 5, 2021 14:34 nikita.ppv@gmail.com (Nikita Popov)
On Sat, Jun 5, 2021 at 4:28 PM Benjamin Eberlei <kontakt@beberlei.de> wrote:

> > > On Fri, Jun 4, 2021 at 5:20 PM Nikita Popov ppv@gmail.com> wrote: > >> Hi internals, >> >> I'd like to open the discussion on readonly properties: >> https://wiki.php.net/rfc/readonly_properties_v2 >> >> This proposal is similar to the >> https://wiki.php.net/rfc/write_once_properties RFC that has been declined >> previously. One significant difference is that the new RFC limits the >> scope >> of initializing assignments. I think a key mistake of the previous RFC was >> the confusing "write-once" framing, which is both technically correct and >> quite irrelevant. >> >> Please see the rationale section ( >> https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this >> proposal relates to other RFCs and alternatives. >> > > I have a question about Reflection support, the RFC states: > > > ReflectionProperty::setValue() can bypass the requirement that > initialization occurs from the scope where the property has been declared. > However, reflection cannot modify a readonly property that has already been > initialized. > > Is there a reason why this is not possible? I am thinking about ORMs or > Deserializers here where a pattern would be: > > class MyDataObject > { > public function __construct( > public readonly $foo > ) {} > } > > $dataObject = $reflectionClass->newInstanceWithoutConstructor(); > $dataObject->getProperty('foo')->setValue($row['value']); >
This example is compatible with the proposal. Calling newInstanceWithoutConstructor() leaves the property uninitialized, and you can initialize it through reflection (or closure rebinding) from any scope. Hydration and deserialization only requires the ability to initialize, not to change the value of properties past initialization. Regards, Nikita
  114739
June 5, 2021 14:29 kontakt@beberlei.de (Benjamin Eberlei)
On Fri, Jun 4, 2021 at 5:20 PM Nikita Popov ppv@gmail.com> wrote:

> Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2 > > This proposal is similar to the > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section ( > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives. >
I have a question about Reflection support, the RFC states:
> ReflectionProperty::setValue() can bypass the requirement that initialization occurs from the scope where the property has been declared.
However, reflection cannot modify a readonly property that has already been initialized. Is there a reason why this is not possible? I am thinking about ORMs or Deserializers here where a pattern would be: class MyDataObject { public function __construct( public readonly $foo ) {} } $dataObject = $reflectionClass->newInstanceWithoutConstructor(); $dataObject->getProperty('foo')->setValue($row['value']);
> > Regards, > Nikita >
  114741
June 5, 2021 16:51 larry@garfieldtech.com ("Larry Garfield")
On Fri, Jun 4, 2021, at 10:19 AM, Nikita Popov wrote:
> Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2 > > This proposal is similar to the > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section ( > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives. > > Regards, > Nikita
Thank you for the detailed analysis in the rationale section. I am, however, still skeptical of this approach, for a couple of reasons. 1. This does appear to address the "leakage" problem I noted in my earlier analysis around the start of the year, when considering the writeonce proposal[1][2]. That's great to see. 2. It doesn't address the larger question of cloning, however. The answer for now seems "maybe we'll get clone-with at some point", which would be a way around it, but there is no active RFC for that right now. I'm obviously very in favor of RFCs that complement each other to give more than the sum of their parts, but those RFCs need to be at least on the horizon to actually come together. Right now that looks like it won't happen this cycle. Absent clone-with, readonly would be effectively unusable in any evolvable object of any non-trivial complexity. It also wouldn't work with objects that need properties that are not constructor arguments, such as PSR-7 type objects. That's a no in my book. 3. As noted in my previous analysis, even with clone-with, asymmetric visibility results in a nicer syntax when evolving objects that have any complexity to them. (See the examples in [2].) 4. One detail left out of the rationale section is how much of a performance difference there is between readonly and implicit-accessors. It says the latter still has a performance hit, but not how much. How significant is it? If it's tiny, then frankly the biggest argument for readonly goes away, since implicit-accessors gives us 98% the same functionality in a more forward-compatible way without the cloning issues. If it's twice as slow, then having a separate keyword for a common case makes sense. 5. I would have to experiment a bit with hydration, as others have noted, because unconventional object construction mechanisms are a critically important workflow. The RFC even notes in passing that serialization is possibly made complicated. Just how complicated? There's no mention of __deserialize() and how it would interact, but cases like that need to be very clearly handled and documented. 6. I'm OK with the approach to constructor promotion and default values. That seems reasonable in context, especially if combined with New-in-initializers[3], which I am hoping you plan to finish this cycle. :-) 7. Though, it just occurred to me, this may result in issues around optional values. class URL { public function __construct( public readonly string $scheme = 'https', public readonly string $domain = '', public readonly int $port = '443', public readonly string $path = '', ) {} } Now, if you want to hydrate the object externally, you need to ensure those properties are not set by the constructor. However, a promoted value is always set by the constructor; either it has a default or it is required. From previous replies it sounds like the workaround for that is reflection and newWithoutConstructor(), but I'm not sure how I feel about that, because that would also then preclude any *other* logic in the constructor, as that would also get skipped. Perhaps this isn't as big of an issue as I think it is, and if so I'd love to hear why, but it makes me concerned. 8. Although the RFC says it does not preclude accessors or explicit asymmetric visibility in the future, and I absolutely believe that Nikita is genuine about that, I am still concerned that should more robust proposals come forward later, it will be met with "we don't need another still-fancier syntax here, readonly is good enough." It's good to know that C# manages to have both, but that doesn't mean the same logic would apply in PHP, or to PHP voters, specifically. 9. I know that the variance for properties in child classes was a source of discussion, and the end result seems to be that readonly-ness is invariant. However, that precludes having an easy way to have both a mutable and immutable version of a class, easily. (If you wanted to have, say, a read-only class most of the time for safety, but for writing you use an alternate "open" version that can be updated and then persisted to the database.) That's a style I've experimented with on and off for a while and already don't have a great solution to, but it feels like readonly would make that even harder. Again, I'd be very happy to hear alternatives around that. Depending on the answers to the above, I could be convinced of this as a stop gap iff paired with clone-with in the same version. However, on its own I think this is only a half-solution, and I'm not wild about a half-solution for a version or two. That's why I prefer going straight to asymmetric visibility, as that would cover mostly the same use case in a single RFC while still being forward compatible; it would benefit from clone-with, but still be usable without it. [1] https://peakd.com/hive-168588/@crell/object-properties-and-immutability [2] https://peakd.com/hive-168588/@crell/object-properties-part-2-examples [3] https://wiki.php.net/rfc/new_in_initializers --Larry Garfield
  114747
June 5, 2021 23:48 mike@newclarity.net (Mike Schinkel)
> On Jun 5, 2021, at 12:51 PM, Larry Garfield <larry@garfieldtech.com> wrote: > > On Fri, Jun 4, 2021, at 10:19 AM, Nikita Popov wrote: >> Hi internals, >> >> I'd like to open the discussion on readonly properties: >> https://wiki.php.net/rfc/readonly_properties_v2 >> >> This proposal is similar to the >> https://wiki.php.net/rfc/write_once_properties RFC that has been declined >> previously. One significant difference is that the new RFC limits the scope >> of initializing assignments. I think a key mistake of the previous RFC was >> the confusing "write-once" framing, which is both technically correct and >> quite irrelevant. >> >> Please see the rationale section ( >> https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this >> proposal relates to other RFCs and alternatives. >> >> Regards, >> Nikita > > Thank you for the detailed analysis in the rationale section. I am, however, still skeptical of this approach, for a couple of reasons. > > 1. This does appear to address the "leakage" problem I noted in my earlier analysis around the start of the year, when considering the writeonce proposal[1][2]. That's great to see. > > 2. It doesn't address the larger question of cloning, however. The answer for now seems "maybe we'll get clone-with at some point", which would be a way around it, but there is no active RFC for that right now. I'm obviously very in favor of RFCs that complement each other to give more than the sum of their parts, but those RFCs need to be at least on the horizon to actually come together. Right now that looks like it won't happen this cycle. Absent clone-with, readonly would be effectively unusable in any evolvable object of any non-trivial complexity. It also wouldn't work with objects that need properties that are not constructor arguments, such as PSR-7 type objects. That's a no in my book.
Larry, To address the cloning objection to raise to Nikita's RFC there is one solution you did not mention in your blog post[1. Rather than your clone-with using a syntax that does not exist anywhere else[2] in PHP we could instead use one similar to your preferred "clone-with" approach where a closure could be run in an internal context. This closure would give the best of both your clone-with and of __clone() approaches. Applying this approach to your example in [3] gives us: $r6 = clone $r5 with function($obj) { $obj->uri = new Uri('https://python.org/'); $obj->headers = ['host' => 'http://java.com/']; $obj->version = 'the old one'; }; If PHP Internals were to approve the "Auto-capturing multi-statement closures" RFC[4] we could also have auto-capture in a place where it would be likely common: $uri = new Uri('https://python.org/'); $headers = [host => 'http://java.com/']; $version = 'the old one'; $r6 = clone $r5 with fn($obj) { $obj->uri = $uri; $obj->headers = $headers; $obj->version = $version; }; Another improvement, which I don't know if it is possible or even advisable, is if the closure could have automatic access to a $this variable for the instance of the cloned object then it becomes even simpler: $uri = new Uri('https://python.org/'); $headers = [host: 'http://java.com/']; $version = 'the old one'; $r6 = clone $r5 with fn() { $this->uri = $uri; $this->headers = $headers; $this->version = $version; }; Interestingly, using your example[2] again, if we were to adopt the same syntax for `new` statements we could obviate the need for tedious and verbose "wither" methods as well as the numerous internal clones and throwaway variables needed with withers called immediately after instantiation. For example: $r2 = new Request() with fn() { $this->method = 'POST'; $this->uri = new Uri('https://php.net/'); $this->protocolVersion = '2.0'; $this->header = [ 'cache' => 'none' ]; }; Lastly, you proposed "with" as your keyword to identify the initialization values which I assume you picked it because of the "wither" method pattern, and my examples above followed suit. However, given `with` is not currently a reserved word in PHP and using a closure inherently provides an existing keyword, PHP could leverage `function` and/or `fn` to indicate "with." (Note the following example do not include the two (2) enhancements mentioned above using fn and $this): $r6 = clone $r5 function($obj) {...}; -Mike [1] https://peakd.com/hive-168588/@crell/object-properties-and-immutability <https://peakd.com/hive-168588/@crell/object-properties-and-immutability> [2] Even though I would very much like to see that syntax become a general purpose syntax for object instantiation, I think it would not be good to use it unless it were fully fleshed out for much greater utility. [3] https://peakd.com/hive-168588/@crell/object-properties-part-2-examples <https://peakd.com/hive-168588/@crell/object-properties-part-2-examples> [4] https://wiki.php.net/rfc/auto-capture-closure <https://wiki.php.net/rfc/auto-capture-closure>
  114760
June 7, 2021 09:06 nikita.ppv@gmail.com (Nikita Popov)
On Sat, Jun 5, 2021 at 6:51 PM Larry Garfield <larry@garfieldtech.com>
wrote:

> On Fri, Jun 4, 2021, at 10:19 AM, Nikita Popov wrote: > > Hi internals, > > > > I'd like to open the discussion on readonly properties: > > https://wiki.php.net/rfc/readonly_properties_v2 > > > > This proposal is similar to the > > https://wiki.php.net/rfc/write_once_properties RFC that has been > declined > > previously. One significant difference is that the new RFC limits the > scope > > of initializing assignments. I think a key mistake of the previous RFC > was > > the confusing "write-once" framing, which is both technically correct and > > quite irrelevant. > > > > Please see the rationale section ( > > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > > proposal relates to other RFCs and alternatives. > > > > Regards, > > Nikita > > Thank you for the detailed analysis in the rationale section. I am, > however, still skeptical of this approach, for a couple of reasons. > > 1. This does appear to address the "leakage" problem I noted in my earlier > analysis around the start of the year, when considering the writeonce > proposal[1][2]. That's great to see. > > 2. It doesn't address the larger question of cloning, however. The answer > for now seems "maybe we'll get clone-with at some point", which would be a > way around it, but there is no active RFC for that right now. I'm > obviously very in favor of RFCs that complement each other to give more > than the sum of their parts, but those RFCs need to be at least on the > horizon to actually come together. Right now that looks like it won't > happen this cycle. Absent clone-with, readonly would be effectively > unusable in any evolvable object of any non-trivial complexity.
Compared to the general area of applicability of readonly properties, the cases that require clone-with are rare. It's the intersection of "not-really-immutable objects using wither evolution" and "has so many properties that passing them to the constructor is infeasible". While this intersection does include a couple of prominent examples, they are by no means numerous. While it may be a problem worth solving, I *personally* don't consider it neither particularly important nor time critical. For what it's worth, I believe Mate wants to work on clone-with once this RFC passes -- while there is no RFC for that, there is already an implementation, though from a cursory look it would require some adjustments to actually work with readonly properties.
> It also wouldn't work with objects that need properties that are not > constructor arguments, such as PSR-7 type objects. That's a no in my book. >
I don't understand what you mean here. Why would properties that are not constructor arguments be a problem?
> 3. As noted in my previous analysis, even with clone-with, asymmetric > visibility results in a nicer syntax when evolving objects that have any > complexity to them. (See the examples in [2].) >
I tend to agree. And I am not opposed to having asymmetric visibility as well. For example, C# does both, and I think both do have value. "readonly" is a very strong signal to the reader. Asymmetric visibility is not. Asymmetric visibility (without proper readonly support) could mean that the property is actually readonly, or it could mean that it's exactly what it says on the tin: The property is internally mutable. I don't think these two concepts should be conflated, though they certainly have overlap.
> 4. One detail left out of the rationale section is how much of a > performance difference there is between readonly and implicit-accessors. > It says the latter still has a performance hit, but not how much. How > significant is it? If it's tiny, then frankly the biggest argument for > readonly goes away, since implicit-accessors gives us 98% the same > functionality in a more forward-compatible way without the cloning issues. > If it's twice as slow, then having a separate keyword for a common case > makes sense. >
The performance difference shouldn't be large, maybe 10-15%. I don't consider performance a big deciding factor here. When comparing to accessors the relevant difference I see is that readonly properties cover a large use case at a small fraction of language complexity.
> 5. I would have to experiment a bit with hydration, as others have noted, > because unconventional object construction mechanisms are a critically > important workflow. The RFC even notes in passing that serialization is > possibly made complicated. Just how complicated? There's no mention of > __deserialize() and how it would interact, but cases like that need to be > very clearly handled and documented. >
The RFC notes in passing that *if* readonly default values were allowed, it could have impact on userland deserialization. As-is, I believe the proposal doesn't present any problem for userland hydrators or deserializers, or PHP's own unserialization mechanisms. This should get mentioned in the proposal, though it will be along the lines of "yes, everything works as expected".
> 6. I'm OK with the approach to constructor promotion and default values. > That seems reasonable in context, especially if combined with > New-in-initializers[3], which I am hoping you plan to finish this cycle. :-) > > 7. Though, it just occurred to me, this may result in issues around > optional values. > > class URL { > public function __construct( > public readonly string $scheme = 'https', > public readonly string $domain = '', > public readonly int $port = '443', > public readonly string $path = '', > ) {} > } > > Now, if you want to hydrate the object externally, you need to ensure > those properties are not set by the constructor. However, a promoted value > is always set by the constructor; either it has a default or it is > required. From previous replies it sounds like the workaround for that is > reflection and newWithoutConstructor(), but I'm not sure how I feel about > that, because that would also then preclude any *other* logic in the > constructor, as that would also get skipped. Perhaps this isn't as big of > an issue as I think it is, and if so I'd love to hear why, but it makes me > concerned. >
Using newInstanceWithoutConstructor() is not a workaround, to the best of my knowledge these things are always implemented this way. You let the constructor initialize the properties, or you bypass the constructor and manually initialize them. I don't really see how it could work in any other way, unless you have some very specific contract between your constructor and a hydrator library (in which case you should make that contract with a method other than the constructor). 8. Although the RFC says it does not preclude accessors or explicit
> asymmetric visibility in the future, and I absolutely believe that Nikita > is genuine about that, I am still concerned that should more robust > proposals come forward later, it will be met with "we don't need another > still-fancier syntax here, readonly is good enough." It's good to know > that C# manages to have both, but that doesn't mean the same logic would > apply in PHP, or to PHP voters, specifically. >
To clarify, I think your argument here goes roughly like this (correct me if I'm wrong): If we already have one of readonly properties or asymmetric visibility, an argument could be made for not adding the other one because it doesn't provide sufficient marginal value. So, if we assume that we can only have one of these features, is readonly properties the right one to add? Shouldn't we add asymmetric visibility instead, which can be used for a wider range of use cases? I think this is a legitimate concern to have, but (under the assumption that we can have only one) it's not easy to say which would be preferable. Yes, asymmetric visibility is more widely applicable, but at the same time I would expect most applications to be bonafide readonly properties. So while asymmetric visibility is more widely applicable, it's also imprecise for the majority use case. Which is better? Of course, my assumption here could also be wrong. 9. I know that the variance for properties in child classes was a source of
> discussion, and the end result seems to be that readonly-ness is > invariant. However, that precludes having an easy way to have both a > mutable and immutable version of a class, easily. (If you wanted to have, > say, a read-only class most of the time for safety, but for writing you use > an alternate "open" version that can be updated and then persisted to the > database.) That's a style I've experimented with on and off for a while > and already don't have a great solution to, but it feels like readonly > would make that even harder. Again, I'd be very happy to hear alternatives > around that. >
I don't believe you can't have an immutable class extend a mutable one or a mutable extend an immutable one, both result in LSP violations (though not on the type level). There was a lot of discussion around this when DateTimeImmutable was introduced, which notably is not in an inheritance relationship (in other direction) with DateTime, for good reason.
> Depending on the answers to the above, I could be convinced of this as a > stop gap iff paired with clone-with in the same version. However, on its > own I think this is only a half-solution, and I'm not wild about a > half-solution for a version or two. That's why I prefer going straight to > asymmetric visibility, as that would cover mostly the same use case in a > single RFC while still being forward compatible; it would benefit from > clone-with, but still be usable without it. >
Just to reiterate this point: Readonly properties is not a half solution to which asymmetric visibility is the full solution. They are *different* solutions to different albeit overlapping problems. I'm not proposing readonly properties here because it's the only thing I can get into 8.1, I'm proposing it because I think it's the more important problem to solve. Regards, Nikita
  114769
June 7, 2021 14:08 larry@garfieldtech.com ("Larry Garfield")
On Mon, Jun 7, 2021, at 4:06 AM, Nikita Popov wrote:
> On Sat, Jun 5, 2021 at 6:51 PM Larry Garfield <larry@garfieldtech.com> > wrote:
> > Thank you for the detailed analysis in the rationale section. I am, > > however, still skeptical of this approach, for a couple of reasons. > > > > 1. This does appear to address the "leakage" problem I noted in my earlier > > analysis around the start of the year, when considering the writeonce > > proposal[1][2]. That's great to see. > > > > 2. It doesn't address the larger question of cloning, however. The answer > > for now seems "maybe we'll get clone-with at some point", which would be a > > way around it, but there is no active RFC for that right now. I'm > > obviously very in favor of RFCs that complement each other to give more > > than the sum of their parts, but those RFCs need to be at least on the > > horizon to actually come together. Right now that looks like it won't > > happen this cycle. Absent clone-with, readonly would be effectively > > unusable in any evolvable object of any non-trivial complexity. > > > Compared to the general area of applicability of readonly properties, the > cases that require clone-with are rare. It's the intersection of > "not-really-immutable objects using wither evolution" and "has so many > properties that passing them to the constructor is infeasible". While this > intersection does include a couple of prominent examples, they are by no > means numerous. While it may be a problem worth solving, I *personally* > don't consider it neither particularly important nor time critical. > > For what it's worth, I believe Mate wants to work on clone-with once this > RFC passes -- while there is no RFC for that, there is already an > implementation, though from a cursory look it would require some > adjustments to actually work with readonly properties.
I look forward to it. I don't think the use cases for non-trivial structs (where a withX() method can just return new static(list all properties here) ) are as few as you think. One difficulty in determining that is that, if they are made easier/more robust (via any of the mechanisms under discussion), their usage is likely to increase. That's a good thing, and a benefit of all of these pieces floating around, but it does make it harder to gauge the net-use of any particular feature. I prefer to err on the side of enabling more flexibility and power rather than less.
> > It also wouldn't work with objects that need properties that are not > > constructor arguments, such as PSR-7 type objects. That's a no in my book. > > > > I don't understand what you mean here. Why would properties that are not > constructor arguments be a problem?
Without clone-with, if you wanted a with-er method, it would necessarily look like this: class Point { public function __construct( public readonly int $x, public readonly int $y, public readonly int $z, ) {} public function withX(int $newX) { return new static($newX, $this->y, $this->z); } } That is, you're populating the entire property set via the constructor. In some cases that's fine; the larger the object, though, the more needlessly verbose redundancy that has. Now let's add a "rendered" flag property, that is public readonly bool. (Maybe not the best example; it's before 9 am and I'm trying to think on the fly here.) It's not in the constructor, but for later processing. It would get set as a side effect of some other method, to indicate the point has already been drawn. Now make a new version of the object with a different Z. What do you do with $rendered?
> > 3. As noted in my previous analysis, even with clone-with, asymmetric > > visibility results in a nicer syntax when evolving objects that have any > > complexity to them. (See the examples in [2].) > > > > I tend to agree. And I am not opposed to having asymmetric visibility as > well. For example, C# does both, and I think both do have value. "readonly" > is a very strong signal to the reader. Asymmetric visibility is not. > Asymmetric visibility (without proper readonly support) could mean that the > property is actually readonly, or it could mean that it's exactly what it > says on the tin: The property is internally mutable. I don't think these > two concepts should be conflated, though they certainly have overlap.
I think this is the crux of the disagreement. There's a large swath of cases that could be well-handled by either. I feel the cases that are only or better handled by asymmetric visibility is larger than the cases that are only or better handled by readonly. As noted above, I don't know that either of us have any data with which to determine which is actually the case. (Maybe someone with more C# experience can speak to that? What's typical there, where both mechanisms are available?)
> > 5. I would have to experiment a bit with hydration, as others have noted, > > because unconventional object construction mechanisms are a critically > > important workflow. The RFC even notes in passing that serialization is > > possibly made complicated. Just how complicated? There's no mention of > > __deserialize() and how it would interact, but cases like that need to be > > very clearly handled and documented. > > > > The RFC notes in passing that *if* readonly default values were allowed, it > could have impact on userland deserialization. As-is, I believe the > proposal doesn't present any problem for userland hydrators or > deserializers, or PHP's own unserialization mechanisms. This should get > mentioned in the proposal, though it will be along the lines of "yes, > everything works as expected". > > > > 6. I'm OK with the approach to constructor promotion and default values. > > That seems reasonable in context, especially if combined with > > New-in-initializers[3], which I am hoping you plan to finish this cycle. :-)
> 8. Although the RFC says it does not preclude accessors or explicit > > asymmetric visibility in the future, and I absolutely believe that Nikita > > is genuine about that, I am still concerned that should more robust > > proposals come forward later, it will be met with "we don't need another > > still-fancier syntax here, readonly is good enough." It's good to know > > that C# manages to have both, but that doesn't mean the same logic would > > apply in PHP, or to PHP voters, specifically. > > > > To clarify, I think your argument here goes roughly like this (correct me > if I'm wrong): If we already have one of readonly properties or asymmetric > visibility, an argument could be made for not adding the other one because > it doesn't provide sufficient marginal value. So, if we assume that we can > only have one of these features, is readonly properties the right one to > add? Shouldn't we add asymmetric visibility instead, which can be used for > a wider range of use cases?
That is essentially correct. I know you're not making that argument, and I'm not making that argument, but it's an argument I can absolutely see being made, because it has been in the past for other things, and sometimes I agree with it as it can be a valid argument. Hence why, in general, I prefer to go with the more robust, more "interacts nicely with other things" option from the get-go, to both not make more robust options harder to adopt in the future and to avoid user confusion down the road when they have 3 different tools that all do *mostly* the same thing and aren't sure of the subtle reasons why they'd use one over the other. (Like, for example, if one had a readonly property but wanted to convert it to a custom accessor property... what are the subtle knock on effects they have to account for, then? There are almost certainly more of them than if switching a property from implicit accessors to explicit accessors, although off hand I don't know what they would all be.)
> I think this is a legitimate concern to have, but (under the assumption > that we can have only one) it's not easy to say which would be preferable. > Yes, asymmetric visibility is more widely applicable, but at the same time > I would expect most applications to be bonafide readonly properties. So > while asymmetric visibility is more widely applicable, it's also imprecise > for the majority use case. Which is better? Of course, my assumption here > could also be wrong.
As above, I'd love to get some actual data from the C# community on this. Lacking that, we're all just guessing.
> 9. I know that the variance for properties in child classes was a source of > > discussion, and the end result seems to be that readonly-ness is > > invariant. However, that precludes having an easy way to have both a > > mutable and immutable version of a class, easily. (If you wanted to have, > > say, a read-only class most of the time for safety, but for writing you use > > an alternate "open" version that can be updated and then persisted to the > > database.) That's a style I've experimented with on and off for a while > > and already don't have a great solution to, but it feels like readonly > > would make that even harder. Again, I'd be very happy to hear alternatives > > around that. > > > > I don't believe you can't have an immutable class extend a mutable one or a > mutable extend an immutable one, both result in LSP violations (though not > on the type level). There was a lot of discussion around this when > DateTimeImmutable was introduced, which notably is not in an inheritance > relationship (in other direction) with DateTime, for good reason.
True. Might that be feasible with asymmetric visibility, however? (I'm honestly not sure.)
> > Depending on the answers to the above, I could be convinced of this as a > > stop gap iff paired with clone-with in the same version. However, on its > > own I think this is only a half-solution, and I'm not wild about a > > half-solution for a version or two. That's why I prefer going straight to > > asymmetric visibility, as that would cover mostly the same use case in a > > single RFC while still being forward compatible; it would benefit from > > clone-with, but still be usable without it. > > > > Just to reiterate this point: Readonly properties is not a half solution to > which asymmetric visibility is the full solution. They are *different* > solutions to different albeit overlapping problems. I'm not proposing > readonly properties here because it's the only thing I can get into 8.1, > I'm proposing it because I think it's the more important problem to solve.
I was perhaps unclear here. I don't think readonly is a half-solution and asymmetric visibility is the full solution. I think readonly without clone-with is a half-solution. readonly plus clone-with is a mostly-full solution that ends up in a very similar (competing?) place with asymmetric visibility, which is a mostly-full solution. I can probably still vote for readonly plus clone-with, because that combination would be useful. It's readonly without clone-with that I am concerned about being a half-solution. (clone-with is a nice-to-have with asymmetric visibility, but IMO is an ergonomic necessity with readonly.) --Larry Garfield
  114779
June 8, 2021 09:18 nikita.ppv@gmail.com (Nikita Popov)
On Mon, Jun 7, 2021 at 4:09 PM Larry Garfield <larry@garfieldtech.com>
wrote:

> On Mon, Jun 7, 2021, at 4:06 AM, Nikita Popov wrote: > > On Sat, Jun 5, 2021 at 6:51 PM Larry Garfield <larry@garfieldtech.com> > > wrote: > > > > Thank you for the detailed analysis in the rationale section. I am, > > > however, still skeptical of this approach, for a couple of reasons. > > > > > > 1. This does appear to address the "leakage" problem I noted in my > earlier > > > analysis around the start of the year, when considering the writeonce > > > proposal[1][2]. That's great to see. > > > > > > 2. It doesn't address the larger question of cloning, however. The > answer > > > for now seems "maybe we'll get clone-with at some point", which would > be a > > > way around it, but there is no active RFC for that right now. I'm > > > obviously very in favor of RFCs that complement each other to give more > > > than the sum of their parts, but those RFCs need to be at least on the > > > horizon to actually come together. Right now that looks like it won't > > > happen this cycle. Absent clone-with, readonly would be effectively > > > unusable in any evolvable object of any non-trivial complexity. > > > > > > Compared to the general area of applicability of readonly properties, the > > cases that require clone-with are rare. It's the intersection of > > "not-really-immutable objects using wither evolution" and "has so many > > properties that passing them to the constructor is infeasible". While > this > > intersection does include a couple of prominent examples, they are by no > > means numerous. While it may be a problem worth solving, I *personally* > > don't consider it neither particularly important nor time critical. > > > > For what it's worth, I believe Mate wants to work on clone-with once this > > RFC passes -- while there is no RFC for that, there is already an > > implementation, though from a cursory look it would require some > > adjustments to actually work with readonly properties. > > I look forward to it. > > I don't think the use cases for non-trivial structs (where a withX() > method can just return new static(list all properties here) ) are as few as > you think. One difficulty in determining that is that, if they are made > easier/more robust (via any of the mechanisms under discussion), their > usage is likely to increase. That's a good thing, and a benefit of all of > these pieces floating around, but it does make it harder to gauge the > net-use of any particular feature. I prefer to err on the side of enabling > more flexibility and power rather than less. > > > > It also wouldn't work with objects that need properties that are not > > > constructor arguments, such as PSR-7 type objects. That's a no in my > book. > > > > > > > I don't understand what you mean here. Why would properties that are not > > constructor arguments be a problem? > > Without clone-with, if you wanted a with-er method, it would necessarily > look like this: > > class Point { > public function __construct( > public readonly int $x, > public readonly int $y, > public readonly int $z, > ) {} > > public function withX(int $newX) { > return new static($newX, $this->y, $this->z); > } > } > > That is, you're populating the entire property set via the constructor. > In some cases that's fine; the larger the object, though, the more > needlessly verbose redundancy that has. > > Now let's add a "rendered" flag property, that is public readonly bool. > (Maybe not the best example; it's before 9 am and I'm trying to think on > the fly here.) It's not in the constructor, but for later processing. It > would get set as a side effect of some other method, to indicate the point > has already been drawn. Now make a new version of the object with a > different Z. What do you do with $rendered? > > > > 3. As noted in my previous analysis, even with clone-with, asymmetric > > > visibility results in a nicer syntax when evolving objects that have > any > > > complexity to them. (See the examples in [2].) > > > > > > > I tend to agree. And I am not opposed to having asymmetric visibility as > > well. For example, C# does both, and I think both do have value. > "readonly" > > is a very strong signal to the reader. Asymmetric visibility is not. > > Asymmetric visibility (without proper readonly support) could mean that > the > > property is actually readonly, or it could mean that it's exactly what it > > says on the tin: The property is internally mutable. I don't think these > > two concepts should be conflated, though they certainly have overlap. > > I think this is the crux of the disagreement. There's a large swath of > cases that could be well-handled by either. I feel the cases that are only > or better handled by asymmetric visibility is larger than the cases that > are only or better handled by readonly. As noted above, I don't know that > either of us have any data with which to determine which is actually the > case. > > (Maybe someone with more C# experience can speak to that? What's typical > there, where both mechanisms are available?) > > > > 5. I would have to experiment a bit with hydration, as others have > noted, > > > because unconventional object construction mechanisms are a critically > > > important workflow. The RFC even notes in passing that serialization > is > > > possibly made complicated. Just how complicated? There's no mention > of > > > __deserialize() and how it would interact, but cases like that need to > be > > > very clearly handled and documented. > > > > > > > The RFC notes in passing that *if* readonly default values were allowed, > it > > could have impact on userland deserialization. As-is, I believe the > > proposal doesn't present any problem for userland hydrators or > > deserializers, or PHP's own unserialization mechanisms. This should get > > mentioned in the proposal, though it will be along the lines of "yes, > > everything works as expected". > > > > > > > 6. I'm OK with the approach to constructor promotion and default > values. > > > That seems reasonable in context, especially if combined with > > > New-in-initializers[3], which I am hoping you plan to finish this > cycle. :-) > > > 8. Although the RFC says it does not preclude accessors or explicit > > > asymmetric visibility in the future, and I absolutely believe that > Nikita > > > is genuine about that, I am still concerned that should more robust > > > proposals come forward later, it will be met with "we don't need > another > > > still-fancier syntax here, readonly is good enough." It's good to know > > > that C# manages to have both, but that doesn't mean the same logic > would > > > apply in PHP, or to PHP voters, specifically. > > > > > > > To clarify, I think your argument here goes roughly like this (correct me > > if I'm wrong): If we already have one of readonly properties or > asymmetric > > visibility, an argument could be made for not adding the other one > because > > it doesn't provide sufficient marginal value. So, if we assume that we > can > > only have one of these features, is readonly properties the right one to > > add? Shouldn't we add asymmetric visibility instead, which can be used > for > > a wider range of use cases? > > That is essentially correct. I know you're not making that argument, and > I'm not making that argument, but it's an argument I can absolutely see > being made, because it has been in the past for other things, and sometimes > I agree with it as it can be a valid argument. > > Hence why, in general, I prefer to go with the more robust, more > "interacts nicely with other things" option from the get-go, to both not > make more robust options harder to adopt in the future and to avoid user > confusion down the road when they have 3 different tools that all do > *mostly* the same thing and aren't sure of the subtle reasons why they'd > use one over the other. > > (Like, for example, if one had a readonly property but wanted to convert > it to a custom accessor property... what are the subtle knock on effects > they have to account for, then? There are almost certainly more of them > than if switching a property from implicit accessors to explicit accessors, > although off hand I don't know what they would all be.) > > > I think this is a legitimate concern to have, but (under the assumption > > that we can have only one) it's not easy to say which would be > preferable. > > Yes, asymmetric visibility is more widely applicable, but at the same > time > > I would expect most applications to be bonafide readonly properties. So > > while asymmetric visibility is more widely applicable, it's also > imprecise > > for the majority use case. Which is better? Of course, my assumption here > > could also be wrong. > > As above, I'd love to get some actual data from the C# community on this. > Lacking that, we're all just guessing.>
Well, I think we can get an approximation like this: https://beta.grep.app/search?q=%7B%20get%3B%20private%20set%3B%20%7D&filter[lang][0]=C%23 { get; private set; } 74,281 results https://beta.grep.app/search?q=%7B%20get%3B%20%7D&filter[lang][0]=C%23 { get; } 156,304 results https://beta.grep.app/search?q=readonly&filter[lang][0]=C%23 readonly 409,585 results Note that the "{ get; }" part includes both readonly properties and get-only abstract properties, so that one is an overestimate. Using variants like \{\s+get;\s+private set;\s+\} doesn't change the results materially. Unless my methodology is completely borked, the ratio of readonly to asymmetric visibility seems to be something like 4:1 even as a conservative estimate. Regards, Nikita
  114788
June 8, 2021 18:28 larry@garfieldtech.com ("Larry Garfield")
On Tue, Jun 8, 2021, at 4:18 AM, Nikita Popov wrote:

> > (Like, for example, if one had a readonly property but wanted to convert > > it to a custom accessor property... what are the subtle knock on effects > > they have to account for, then? There are almost certainly more of them > > than if switching a property from implicit accessors to explicit accessors, > > although off hand I don't know what they would all be.) > > > > > I think this is a legitimate concern to have, but (under the assumption > > > that we can have only one) it's not easy to say which would be > > preferable. > > > Yes, asymmetric visibility is more widely applicable, but at the same > > time > > > I would expect most applications to be bonafide readonly properties. So > > > while asymmetric visibility is more widely applicable, it's also > > imprecise > > > for the majority use case. Which is better? Of course, my assumption here > > > could also be wrong. > > > > As above, I'd love to get some actual data from the C# community on this. > > Lacking that, we're all just guessing.> > > > Well, I think we can get an approximation like this: > > https://beta.grep.app/search?q=%7B%20get%3B%20private%20set%3B%20%7D&filter[lang][0]=C%23 > { get; private set; } 74,281 results > https://beta.grep.app/search?q=%7B%20get%3B%20%7D&filter[lang][0]=C%23 { > get; } 156,304 results > https://beta.grep.app/search?q=readonly&filter[lang][0]=C%23 readonly > 409,585 results > > Note that the "{ get; }" part includes both readonly properties and > get-only abstract properties, so that one is an overestimate. Using > variants like \{\s+get;\s+private set;\s+\} doesn't change the results > materially. > > Unless my methodology is completely borked, the ratio of readonly to > asymmetric visibility seems to be something like 4:1 even as a conservative > estimate. > > Regards, > Nikita
Interesting. I have no particular grounds to challenge your methodology, so I'll accept that C# seems to use readonly much more than asymmetric visibility/accessors. Does C# have a clone-with or equivalent? Even with readonly being useful, I think it's only half the answer without a clean evolvability mechanism beyond "re-type all the fields by hand." CPP removed a ton of that boilerplate, and I'd hate to bring it back. --Larry Garfield
  114789
June 8, 2021 22:58 mike@newclarity.net (Mike Schinkel)
> On Jun 8, 2021, at 5:18 AM, Nikita Popov ppv@gmail.com> wrote: > > Well, I think we can get an approximation like this: > > https://beta.grep.app/search?q=%7B%20get%3B%20private%20set%3B%20%7D&filter[lang][0]=C%23 <https://beta.grep.app/search?q={%20get;%20private%20set;%20}&filter[lang][0]=C#> > { get; private set; } 74,281 results > https://beta.grep.app/search?q=%7B%20get%3B%20%7D&filter[lang][0]=C%23 <https://beta.grep.app/search?q={%20get;%20}&filter[lang][0]=C#> { > get; } 156,304 results > https://beta.grep.app/search?q=readonly&filter[lang][0]=C%23 <https://beta.grep.app/search?q=readonly&filter[lang][0]=C#> readonly > 409,585 results > > Note that the "{ get; }" part includes both readonly properties and > get-only abstract properties, so that one is an overestimate. Using > variants like \{\s+get;\s+private set;\s+\} doesn't change the results > materially. > > Unless my methodology is completely borked, the ratio of readonly to > asymmetric visibility seems to be something like 4:1 even as a conservative > estimate.
When you two are speaking of "asymmetric visibility," are you speaking of this RFC[1], this one[2], one I am not mentioning, or one that does not exist yet? If you are referring to [1] there is more to asymmetric visibility than what C# would call "auto-implemented properties[3]." If we search for `get {` we find 266,859 occurrences and another 5765 for `{ set{`. I assume `readonly` would not address either of these? https://beta.grep.app/search?current=2&q=get%5Cs%2A%5C%7B®exp=true&filter[lang][0]=C%23 <https://beta.grep.app/search?current=2&q=get%5Cs%2A%5C%7B®exp=true&filter%5Blang%5D%5B0%5D=C%23> https://beta.grep.app/search?q=%5C%7B%5Cs%2Aset%5Cs%2A%5C%7B®exp=true&format=e&filter[lang][0]=C%23 <https://beta.grep.app/search?q=%5C%7B%5Cs%2Aset%5Cs%2A%5C%7B®exp=true&format=e&filter%5Blang%5D%5B0%5D=C%23> -Mike [1] https://wiki.php.net/rfc/property_accessors <https://wiki.php.net/rfc/property_accessors> [2] https://wiki.php.net/rfc/property_write_visibility <https://wiki.php.net/rfc/property_write_visibility> [3] https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties <https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties> P.S. You also over-counted `readonly` by 44k. If you go with case-sensitive for "readonly" you only get 365k. Seems they like using "ReadOnly" as part of symbol names. https://beta.grep.app/search?q=readonly&case=true&filter[lang][0]=C%23 <https://beta.grep.app/search?q=readonly&case=true&filter%5Blang%5D%5B0%5D=C%23> P.P.S. I'm not taking a position pro- or con- the readonly RFC, just wanting to clarify the stats used by others to evaluate.
  114793
June 9, 2021 10:05 pierre.php@gmail.com (Pierre Joye)
Hi Nikita,

On Fri, Jun 4, 2021 at 10:19 PM Nikita Popov ppv@gmail.com> wrote:
> > Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2
Very good work, thank you :)
> This proposal is similar to the > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section ( > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives.
About this paragraph in https://wiki.php.net/rfc/readonly_properties_v2#rationale: " The addition of readonly properties neither precludes nor discourages the addition of asymmetric property visibility." To me this sentence is the meaning of a readonly property, not an immutable property, as in writeable once property, in constructor. This RFC is perfect given its goal but the property attribute "readonly". How would you define a property only readable from outside the scope but writeable inside the scope of its class (->status f.e.)? This is PHP and we always have our ways. That being said, the keyword "readonly" is really about what is defined as "asymmetric property visibility" in many other languages. Rust uses annotation, .net via either readonly and they use an init setter to make it immutable in v9 (public DateTime RecordedAt { get; init; }), other like java or scala relies on setting a property getters only. Given this, I wonder if it would not be easier to have actual per property getter/setter as an addition or replacement to the current (kind of horrible) get/set($name, $value). This would all cases in one shot, a more complicated shot but much more powerful. For the record, I am totally not a fan of the 'manual" get/setMyProperty ;) In any case, I would at least put a second thought on the name "readonly". ps: sorry if this question has been discussed in previous RFCs, pointers appreciated as I did not find it :) Best, -- Pierre
  114801
June 9, 2021 16:51 larry@garfieldtech.com ("Larry Garfield")
On Wed, Jun 9, 2021, at 5:05 AM, Pierre Joye wrote:
> Hi Nikita, > > On Fri, Jun 4, 2021 at 10:19 PM Nikita Popov ppv@gmail.com> wrote: > > > > Hi internals, > > > > I'd like to open the discussion on readonly properties: > > https://wiki.php.net/rfc/readonly_properties_v2 > > Very good work, thank you :) > > > This proposal is similar to the > > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > > previously. One significant difference is that the new RFC limits the scope > > of initializing assignments. I think a key mistake of the previous RFC was > > the confusing "write-once" framing, which is both technically correct and > > quite irrelevant. > > > > Please see the rationale section ( > > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > > proposal relates to other RFCs and alternatives. > > About this paragraph in > https://wiki.php.net/rfc/readonly_properties_v2#rationale: > > " The addition of readonly properties neither precludes nor > discourages the addition of asymmetric property visibility." > > To me this sentence is the meaning of a readonly property, not an > immutable property, as in writeable once property, in constructor. > > This RFC is perfect given its goal but the property attribute "readonly". > > How would you define a property only readable from outside the scope > but writeable inside the scope of its class (->status f.e.)? > > This is PHP and we always have our ways. That being said, the keyword > "readonly" is really about what is defined as "asymmetric property > visibility" in many other languages. Rust uses annotation, .net via > either readonly and they use an init setter to make it immutable in > v9 (public DateTime RecordedAt { get; init; }), other like java or > scala relies on setting a property getters only. > > Given this, I wonder if it would not be easier to have actual per > property getter/setter as an addition or replacement to the current > (kind of horrible) get/set($name, $value). This would all cases in > one shot, a more complicated shot but much more powerful. For the > record, I am totally not a fan of the 'manual" get/setMyProperty ;) > > In any case, I would at least put a second thought on the name "readonly". > > ps: sorry if this question has been discussed in previous RFCs, > pointers appreciated as I did not find it :) > > Best, > -- > Pierre
Pierre and Mike: "Asymmetric visibility" as we keep referring to it would mean the "implicit accessors only" version of this: https://wiki.php.net/rfc/property_accessors That is, it would let you define public/private/protected for get and set operations on a property separately from each other. There are three key differences between readonly and asymmetric visibility as described there: * Asymmetric visibility would allow a property to be reassigned multiple times from within a class, readonly would allow writing to it only once when it's uninitialized. Whether one of those is too-tight or too-loose is a matter of opinion and context. * Asymmetric visibility is deliberately a syntax that is forward-compatible with explicit accessor methods in the future. readonly would be a separate, independent feature/syntax. * Asymmetric visibility, IMO, is a stand-alone useful feature. readonly would be most useful if combined with a separate clone-with operator (discussed elsewhere). Whether readonly is useful enough on its own without that to justify its passage without a clone-with RFC also under discussion is an open question, and largely what we've been debating. Both approaches would eliminate *most* uses of __get/__set, which I think everyone agrees is a win. (Nikita, I hope I represented that accurately.) --Larry Garfield
  114803
June 10, 2021 01:21 pierre.php@gmail.com (Pierre Joye)
Good morning Larry,

Thank you. Very good summary, maybe worth adding to the RFC :)

On Wed, Jun 9, 2021 at 11:52 PM Larry Garfield <larry@garfieldtech.com> wrote:

> readonly would be a separate, independent feature/syntax.
Yes, my thought is about self explanatory syntax matching common usage of the same syntax/keyword with other languages. In this case, readonly, it does not :) Absolutely not a big deal (one will google that more ;), however I do like some "consistency" across languages as we almost all rely on many and get used to syntax for very standard operations like this. Best, -- Pierre @pierrejoye
  114807
June 10, 2021 07:31 nikita.ppv@gmail.com (Nikita Popov)
On Thu, Jun 10, 2021 at 3:21 AM Pierre Joye php@gmail.com> wrote:

> Good morning Larry, > > Thank you. Very good summary, maybe worth adding to the RFC :) > > On Wed, Jun 9, 2021 at 11:52 PM Larry Garfield <larry@garfieldtech.com> > wrote: > > > readonly would be a separate, independent feature/syntax. > > Yes, my thought is about self explanatory syntax matching common usage > of the same syntax/keyword with other languages. In this case, > readonly, it does not :) > > Absolutely not a big deal (one will google that more ;), however I do > like some "consistency" across languages as we almost all rely on many > and get used to syntax for very standard operations like this. >
The usage of "readonly" as proposed follows the commonly accepted interpretation in other languages (modulo details). Readonly properties refer to properties that only allow initializing assignments, though what exactly that means is language dependent (e.g. it can take the form of "only assignments inside the constructor"). Both C# and TypeScript use "readonly" in this manner. I'm not sure where you got the strange idea that "readonly" can refer to asymmetric visibility. The two syntactic approaches to asymmetric visibility that I'm aware of are "{ get; private set; }" in C# and "public private(set)" in Swift. Neither use the "readonly" terminology for this purpose. Using "readonly" to describe asymmetric visibility is semantically incorrect, as such properties are not, in fact, read-only. Regards, Nikita
  114809
June 10, 2021 09:33 pierre.php@gmail.com (Pierre Joye)
On Thu, Jun 10, 2021, 2:32 PM Nikita Popov ppv@gmail.com> wrote:

I'm not sure where you got the strange idea that "readonly" can refer to
> asymmetric visibility. The two syntactic approaches to asymmetric > visibility that I'm aware of are "{ get; private set; }" in C# and "public > private(set)" in Swift. Neither use the "readonly" terminology for this > purpose. Using "readonly" to describe asymmetric visibility is semantically > incorrect, as such properties are not, in fact, read-only. >
I listed my refs in the initial reply :) And some like .net updates their syntax for init only write. I suppose I see it more as "from the outside of the class" and my personal usages are like that too. In any case, good rfc, naming perception is a details, important but a details for first time users.
> Regards, > Nikita >
  114804
June 10, 2021 04:08 mike@newclarity.net (Mike Schinkel)
Hi Larry,

Thanks for the response.

> On Jun 9, 2021, at 12:51 PM, Larry Garfield <larry@garfieldtech.com> wrote: > > Pierre and Mike: > > "Asymmetric visibility" as we keep referring to it would mean the "implicit accessors only" version of this: https://wiki.php.net/rfc/property_accessors > > That is, it would let you define public/private/protected for get and set operations on a property separately from each other. > > There are three key differences between readonly and asymmetric visibility as described there: > > * Asymmetric visibility would allow a property to be reassigned multiple times from within a class, readonly would allow writing to it only once when it's uninitialized. Whether one of those is too-tight or too-loose is a matter of opinion and context.
When I read your statement I first thought you were wrong, but then I re-read Nikitia's RFC[1] and realized that RFC stated the properties of an object assigned to a readonly property *could* be updated, but *not* the readonly property itself. I wonder if I was the only one who read that mistakenly? Anyway, have either you or Nikita considered making a distinction between 'public readonly' and 'private readonly' such that one could disallow any changes after initialization and the other could allow changes but only within the class? -Mike [1] https://wiki.php.net/rfc/readonly_properties_v2#proposal
  114806
June 10, 2021 06:36 pierre.php@gmail.com (Pierre Joye)
On Thu, Jun 10, 2021 at 11:08 AM Mike Schinkel <mike@newclarity.net> wrote:
> > Hi Larry, > > Thanks for the response. > > > On Jun 9, 2021, at 12:51 PM, Larry Garfield <larry@garfieldtech.com> wrote:
> > > > Pierre and Mike: > > > > "Asymmetric visibility" as we keep referring to it would mean the "implicit accessors only" version of this:
https://wiki.php.net/rfc/property_accessors
> > > > That is, it would let you define public/private/protected for get and set operations on a property separately from each other.
> > > > There are three key differences between readonly and asymmetric visibility as described there:
> > > > * Asymmetric visibility would allow a property to be reassigned multiple times from within a class, readonly would allow writing to it only
once when it's uninitialized. Whether one of those is too-tight or too-loose is a matter of opinion and context.
> > When I read your statement I first thought you were wrong, but then I re-read Nikitia's RFC[1] and realized that RFC stated the properties of an
object assigned to a readonly property *could* be updated, but *not* the readonly property itself. That's not related but an assignment by "reference" consequence. I think :)
> > I wonder if I was the only one who read that mistakenly? > > Anyway, have either you or Nikita considered making a distinction between 'public readonly' and 'private readonly' such that one could disallow any
changes after initialization and the other could allow changes but only within the class? Better formulated than I tried :) I would like to have the readonly keyword to make a property readonly outside the class but read/write inside the class, together with visibility it should cover pretty much all cases (parent class included). Init only could be a separate keyword (allows init only, inside the class or outside the class being defined by the property visibility attributes, protected, private or public). I think users, and myself first when I read it, will have a hard time translating "asymmetric...." part of this whole thing (as in, if one has to go to wikipedia to understand what something does instead of what it looks like ;) ). best, -- Pierre @pierrejoye