[RFC] [DISCUSSION] Immutable/final/readonly properties

  108675
February 19, 2020 17:05 kocsismate90@gmail.com (=?UTF-8?B?TcOhdMOpIEtvY3Npcw==?=)
Hi Internals,

I'd like to move my RFC forward to the discussion phase:
https://wiki.php.net/rfc/write_once_properties

In short, I propose to add support for a new property modifier that would
allow properties to be initialized, but not modified afterwards.

Cheers,
Máté Kocsis
  108681
February 19, 2020 17:52 ocramius@gmail.com (Marco Pivetta)
Hey Mark,

Asking here as well, so that others are aware: is
`unset($this->aPropertyThatWasNotInitializedBefore)` still working with
this patch?

Also, are the semantics around `__get`, `__set` and `__isset` the same as
with typed properties?

In the RFC, you have `unset($foo->c)` as disallowed: the property isn't
declared nor initialized though. I'd expect this to fail only after first
initialization?

Marco Pivetta

http://twitter.com/Ocramius

http://ocramius.github.com/


On Wed, Feb 19, 2020 at 6:06 PM Máté Kocsis <kocsismate90@gmail.com> wrote:

> Hi Internals, > > I'd like to move my RFC forward to the discussion phase: > https://wiki.php.net/rfc/write_once_properties > > In short, I propose to add support for a new property modifier that would > allow properties to be initialized, but not modified afterwards. > > Cheers, > Máté Kocsis >
  108685
February 19, 2020 18:50 larry@garfieldtech.com ("Larry Garfield")
On Wed, Feb 19, 2020, at 11:05 AM, Máté Kocsis wrote:
> Hi Internals, > > I'd like to move my RFC forward to the discussion phase: > https://wiki.php.net/rfc/write_once_properties > > In short, I propose to add support for a new property modifier that would > allow properties to be initialized, but not modified afterwards. > > Cheers, > Máté Kocsis
As envisoned, does this allow for a property to be set to a dynamic value? My concern is that while a public locked/writeonce property is great for access, it doesn't do anything to enable lazy setting on first access. In fact the only way to do that would be to make it private and wrap access in a method, which would look exactly like that does now but with an extra keyword that doesn't actually offer much. You could set the value in advance in the constructor, but then it's not lazy, just locked. Is there a way it could support lazy-on-first-use then locked? --Larry Garfield
  108688
February 19, 2020 19:36 ocramius@gmail.com (Marco Pivetta)
On Wed, Feb 19, 2020, 19:51 Larry Garfield <larry@garfieldtech.com> wrote:

> On Wed, Feb 19, 2020, at 11:05 AM, Máté Kocsis wrote: > > Hi Internals, > > > > I'd like to move my RFC forward to the discussion phase: > > https://wiki.php.net/rfc/write_once_properties > > > > In short, I propose to add support for a new property modifier that would > > allow properties to be initialized, but not modified afterwards. > > > > Cheers, > > Máté Kocsis > > As envisoned, does this allow for a property to be set to a dynamic > value? My concern is that while a public locked/writeonce property is > great for access, it doesn't do anything to enable lazy setting on first > access. In fact the only way to do that would be to make it private and > wrap access in a method, which would look exactly like that does now but > with an extra keyword that doesn't actually offer much. > > You could set the value in advance in the constructor, but then it's not > lazy, just locked. > > Is there a way it could support lazy-on-first-use then locked?
Máté did run a few teats: operating on a lazy value (before initialisation) seems to work as expected 👍
  108690
February 19, 2020 21:49 larry@garfieldtech.com ("Larry Garfield")
On Wed, Feb 19, 2020, at 1:36 PM, Marco Pivetta wrote:
> On Wed, Feb 19, 2020, 19:51 Larry Garfield <larry@garfieldtech.com> wrote: > > > On Wed, Feb 19, 2020, at 11:05 AM, Máté Kocsis wrote: > > > Hi Internals, > > > > > > I'd like to move my RFC forward to the discussion phase: > > > https://wiki.php.net/rfc/write_once_properties > > > > > > In short, I propose to add support for a new property modifier that would > > > allow properties to be initialized, but not modified afterwards. > > > > > > Cheers, > > > Máté Kocsis > > > > As envisoned, does this allow for a property to be set to a dynamic > > value? My concern is that while a public locked/writeonce property is > > great for access, it doesn't do anything to enable lazy setting on first > > access. In fact the only way to do that would be to make it private and > > wrap access in a method, which would look exactly like that does now but > > with an extra keyword that doesn't actually offer much. > > > > You could set the value in advance in the constructor, but then it's not > > lazy, just locked. > > > > Is there a way it could support lazy-on-first-use then locked? > > > Máté did run a few teats: operating on a lazy value (before initialisation) > seems to work as expected 👍
Erm. I don't think I was clear in my intent. I would expect that operating on one of these properties before it's initialized will throw an error: class Foo { readonly public string $bar; } $f = new Foo(); print $f->bar; // This should fail. My question is whether this will work: class Foo { readonly public string $bar = $this->value(); private function value(): string { return "hello"; } } $f = new Foo(); print $f->bar; // I would want this print "hello". Does that work currently or no? If so, this is pretty sweet. If not, it seems to be of limited use. --Larry Garfield
  108693
February 20, 2020 08:58 kocsismate90@gmail.com (=?UTF-8?B?TcOhdMOpIEtvY3Npcw==?=)
> > I would expect that operating on one of these properties before it's > initialized will throw an error: >
Actually, the RFC only says that the "immutability" of properties is guaranteed after initialization. We could of course change this premise, but that would make some important use-cases (like what Marco's ProxyManager has) impossible to do. Furthermore, unsetting properties could be completely disabled by another RFC, given there is a migration path for the legitimiate use-cases. As far as I remember, the "Locked Classes" ( https://wiki.php.net/rfc/locked-classes) and lately the "Rigid Properties" (https://github.com/php/php-src/pull/5170) RFCs try to have their shots at it.
> Does that work currently or no? If so, this is pretty sweet. If not, it > seems to be of limited use. >
No, it doesn't work, and it seems to be an unrelated feature for me. As far as I understand your example, it's the topic of the "Constant expressions" RFC. I believe the behaviour proposed by my RFC would be still useful in many cases where one wants to be sure that no unexpected modifications can happen with a property. My use-cases would mainly include objects storing different kind of data: events, value objects, data transfer objects. Máté Kocsis
  108692
February 20, 2020 00:26 matthewmatthew@gmail.com (Matthew Brown)
Someone recently requested something similar for a PHP static analysis tool
I wrote (https://psalm.dev/r/f75997a263), though that version only allows
lazy initialisation inside the class in which a given property is declared.

Personally I don't like either approach – I think per-property getters and
setters would be a more appropriate venue for this functionality, something
like:

property int $s {
    public get;
    private set;
}

This pattern could also be extended to support per-property getter and
setter methods.

On Wed, 19 Feb 2020 at 12:06, Máté Kocsis <kocsismate90@gmail.com> wrote:

> Hi Internals, > > I'd like to move my RFC forward to the discussion phase: > https://wiki.php.net/rfc/write_once_properties > > In short, I propose to add support for a new property modifier that would > allow properties to be initialized, but not modified afterwards. > > Cheers, > Máté Kocsis >
  108694
February 20, 2020 09:06 nikita.ppv@gmail.com (Nikita Popov)
On Thu, Feb 20, 2020 at 1:27 AM Matthew Brown <matthewmatthew@gmail.com>
wrote:

> Someone recently requested something similar for a PHP static analysis tool > I wrote (https://psalm.dev/r/f75997a263), though that version only allows > lazy initialisation inside the class in which a given property is declared. > > Personally I don't like either approach – I think per-property getters and > setters would be a more appropriate venue for this functionality, something > like: > > property int $s { > public get; > private set; > } > > This pattern could also be extended to support per-property getter and > setter methods. >
While I certainly like the idea of per-property getters/setters, I think that both of these have their place. This RFC proposes readonly properties, that can be initialized once, and then never modified again, even within the same class. Getters/setters only provide the possibility of having asymmetric visibility: They prevent modifying the property from outside the class, but it can still be modified inside the class. In that, readonly properties offer a stronger guarantee. Of course, that does leave the question of how often you need one or the other. Maybe just the asymmetric visibility is sufficient for most practical purposes, in which case it may not be worthwhile to introduce readonly properties as a separate feature. Two comments on the specifics of the RFC: The RFC allows specifying a default value for readonly properties. However, a property for which a default value has been specified will always have that value, as it cannot be overwritten in the constructor. If you write "public readonly int $foo = 42", then $object->foo is *always* going to be 42. I'm not sure what that would ever be useful for, and it seems like something that is bound to be confusing. Maybe it would make more sense to forbid readonly properties with default values? (That way, the rule that readonly properties have to be typed falls out naturally as well -- untyped properties always have a default.) Regarding the keyword choice, I think you can drop "sealed" from the list, as it is an established term that affects inheritance, not mutability. Of the choices you present, "immutable", "readonly" and "writeonce" seem like the most viable candidates. "writeonce", while the one that is most technically accurate, is also *unnecessarily* technically accurate and not intuitive. From the perspective of an API consumer, I think that "readonly" is the most accurate description of how they are supposed to interact with the property. The API contract you want to expose is that they can only read from the property, not write to it. Calling it "writeonce" would be quite confusing in that context, because the API consumer is never expected to write to the property. In the majority of cases you will be providing fully initialized objects, in which case they are indeed readonly for the consumer -- the details of the write-once property are only relevant in special cases like ReflectionClass::newObjectWithoutConstructor() for serialization libraries, or lazy initialization like in Marco's ProxyManager. Regards, Nikita
> On Wed, 19 Feb 2020 at 12:06, Máté Kocsis <kocsismate90@gmail.com> wrote: > > > Hi Internals, > > > > I'd like to move my RFC forward to the discussion phase: > > https://wiki.php.net/rfc/write_once_properties > > > > In short, I propose to add support for a new property modifier that would > > allow properties to be initialized, but not modified afterwards. > > > > Cheers, > > Máté Kocsis > > >
  108704
February 20, 2020 17:39 larry@garfieldtech.com ("Larry Garfield")
On Thu, Feb 20, 2020, at 3:06 AM, Nikita Popov wrote:
> On Thu, Feb 20, 2020 at 1:27 AM Matthew Brown <matthewmatthew@gmail.com> > wrote: > > > Someone recently requested something similar for a PHP static analysis tool > > I wrote (https://psalm.dev/r/f75997a263), though that version only allows > > lazy initialisation inside the class in which a given property is declared. > > > > Personally I don't like either approach – I think per-property getters and > > setters would be a more appropriate venue for this functionality, something > > like: > > > > property int $s { > > public get; > > private set; > > } > > > > This pattern could also be extended to support per-property getter and > > setter methods. > > > > While I certainly like the idea of per-property getters/setters, I think > that both of these have their place. This RFC proposes readonly properties, > that can be initialized once, and then never modified again, even within > the same class. Getters/setters only provide the possibility of having > asymmetric visibility: They prevent modifying the property from outside the > class, but it can still be modified inside the class. In that, readonly > properties offer a stronger guarantee. > > Of course, that does leave the question of how often you need one or the > other. Maybe just the asymmetric visibility is sufficient for most > practical purposes, in which case it may not be worthwhile to introduce > readonly properties as a separate feature. > > Two comments on the specifics of the RFC: > > The RFC allows specifying a default value for readonly properties. However, > a property for which a default value has been specified will always have > that value, as it cannot be overwritten in the constructor. If you write > "public readonly int $foo = 42", then $object->foo is *always* going to be > 42. I'm not sure what that would ever be useful for, and it seems like > something that is bound to be confusing. Maybe it would make more sense to > forbid readonly properties with default values? (That way, the rule that > readonly properties have to be typed falls out naturally as well -- untyped > properties always have a default.) > > Regarding the keyword choice, I think you can drop "sealed" from the list, > as it is an established term that affects inheritance, not mutability. Of > the choices you present, "immutable", "readonly" and "writeonce" seem like > the most viable candidates. "writeonce", while the one that is most > technically accurate, is also *unnecessarily* technically accurate and not > intuitive. From the perspective of an API consumer, I think that "readonly" > is the most accurate description of how they are supposed to interact with > the property. The API contract you want to expose is that they can only > read from the property, not write to it. Calling it "writeonce" would be > quite confusing in that context, because the API consumer is never expected > to write to the property. In the majority of cases you will be providing > fully initialized objects, in which case they are indeed readonly for the > consumer -- the details of the write-once property are only relevant in > special cases like ReflectionClass::newObjectWithoutConstructor() for > serialization libraries, or lazy initialization like in Marco's > ProxyManager. > > Regards, > Nikita
> From Máté Kocsis <kocsismate90@gmail.com> wrote:
> No, it doesn't work, and it seems to be an unrelated feature for me. As far > as I understand your > example, it's the topic of the "Constant expressions" RFC. > > I believe the behaviour proposed by my RFC would be still useful in many > cases where one wants > to be sure that no unexpected modifications can happen with a property.. My > use-cases > would mainly include objects storing different kind of data: events, value > objects, data transfer objects.
Yeah, I'm definitely thinking in relation to the earlier discussion, since I think they're all inter-related. (This, property accessors, and constant expressions.) As Nikita notes above, a read-only property with a default value is... basically a constant already. So that's not really useful. For defined-later readonly properties, I'm not sure how the earlier point about reading an unintialized property isn't valid. Currently: class Foo { public string $bar; } $f = new Foo(); print $f->bar; // this throws a TypeError. I would expect the exact same behavior if $bar were marked readonly/locked/whatever. Are you saying that's not the case? I do think it's fair to bring in the property accessor discussion here, as property accessors would allow for the same net functionality as this property as a special case, albeit with likely more syntax. AFAIR the issue with them before wasn't that people opposed the idea, just the performance impact. If we could address the performance impact, that would give us much more functionality-for-the-buck, including an equivalent of read-only properties including potentially lazy initialization. Or derive-on-demand behavior would also be a big increase in functionality. It's not that I don't see a value to this RFC; I actually have a few places in my own code where I could use it. It's that I see it as being of fairly narrow use, so I'm trying to figure out how to increase it so that the net-win is worth it. --Larry Garfield
  108713
February 21, 2020 10:29 kocsismate90@gmail.com (=?UTF-8?B?TcOhdMOpIEtvY3Npcw==?=)
> > Yeah, I'm definitely thinking in relation to the earlier discussion, since > I think they're all inter-related. (This, property accessors, and constant > expressions.) >
The biggest question is whether it's worth to support both readonly properties and property accessors. My answer is clear yes, because there are many-many ways to mess with private or protected properties without and public setters from the outside - in which case property accessors couldn't help much. I collected some examples I know of: https://3v4l.org/Ta4PM Please note that the first two examples also apply to private properties, while the last one only applies to protected ones.
> As Nikita notes above, a read-only property with a default value is... > basically a constant already. So that's not really useful. >
I agree that they are not very useful, however I wouldn't restrict their usage. Mainly because there are probably some legitimate use-cases, but I also think it would be advantageous to be consistent with the other languages in this case.
> For defined-later readonly properties, I'm not sure how the earlier point > about reading an unintialized property isn't valid. Currently: > > class Foo { > public string $bar; > } > > $f = new Foo(); > print $f->bar; // this throws a TypeError. > > I would expect the exact same behavior if $bar were marked > readonly/locked/whatever. Are you saying that's not the case? >
Sorry if I didn't exactly get the question/example, but what I can tell you is that currently an Error exception is thrown with the "Typed property Foo::$bar must not be accessed before initialization" message, and it would be the case with my patch as well since it doesn't affect the reading side. The situation is the same when it comes to unsetting uninitialized typed properties. Currently, these properties can be unset with no problem (and the __get(), __set() etc. magic methods are then invoked when accessing them), and the same would happen with my patch.
> If we could address the performance impact, that would give us much more > functionality-for-the-buck, including an equivalent of read-only properties > including potentially lazy initialization. Or derive-on-demand behavior > would also be a big increase in functionality. > > It's not that I don't see a value to this RFC; I actually have a few > places in my own code where I could use it. It's that I see it as being of > fairly narrow use, so I'm trying to figure out how to increase it so that > the net-win is worth it. >
The reason why I brought up this RFC is that I'd really like to add first-class support for immutable objects, and it seemed to be a good idea to first go for readonly properties. This way, the scope of an immutable object RFC gets smaller, while it's possible to only have readonly properties alone. Regards, Máté
  108716
February 21, 2020 23:17 larry@garfieldtech.com ("Larry Garfield")
On Fri, Feb 21, 2020, at 4:29 AM, Máté Kocsis wrote:
> > > > Yeah, I'm definitely thinking in relation to the earlier discussion, since > > I think they're all inter-related. (This, property accessors, and constant > > expressions.) > > > > The biggest question is whether it's worth to support both readonly > properties and property accessors. My answer is clear yes, because there > are many-many > ways to mess with private or protected properties without and public > setters from the outside - in which case property accessors couldn't help > much. I collected some examples I know of: > https://3v4l.org/Ta4PM
I didn't even know you could do some of those. That's horrifying. :-)
> > As Nikita notes above, a read-only property with a default value is.... > > basically a constant already. So that's not really useful. > > I agree that they are not very useful, however I wouldn't restrict their > usage. Mainly because there are probably some legitimate use-cases, but I > also think it would > be advantageous to be consistent with the other languages in this case..
If they could do something that class constants can't, that would make them useful. If not, then I feel like it would just be introducing new syntax for the same thing, without much benefit. (I'm thinking of, eg, could you set them by default to a new Foo() object, which you could then modify the Foo but not change it for another object, thus moving that initialization out of the constructor? That sort of thing.)
> > If we could address the performance impact, that would give us much more > > functionality-for-the-buck, including an equivalent of read-only properties > > including potentially lazy initialization. Or derive-on-demand behavior > > would also be a big increase in functionality. > > > > It's not that I don't see a value to this RFC; I actually have a few > > places in my own code where I could use it. It's that I see it as being of > > fairly narrow use, so I'm trying to figure out how to increase it so that > > the net-win is worth it. > > > > The reason why I brought up this RFC is that I'd really like to add > first-class support for immutable objects, and it seemed to be a good idea > to first go for readonly properties. > This way, the scope of an immutable object RFC gets smaller, while it's > possible to only have readonly properties alone. > > Regards, > Máté
I'm totally on board for better value object support, so that's a good motive for me. The question I have is whether this is really a good stepping stone in that direction or if it would lead down a wrong path and lock us into too much TIMTOWTDI (for the Perl fans in the room). So let's think that through down that path. How would write-once properties lead into properly immutable value objects? Or do they give us that themselves? The biggest challenge for immutable objects, IMO, is evolving them. Eg, $result->withContentType(...) to use the PSR-7 example. Would we expect people to do it with a method like that, or would there be some other mechanism? If the properties are public, would we offer a more syntactic way to modify them directly? The with*() method style requires cloning the object. What happens to the locked status of a set property if the object is cloned? Are they then settable again, or do they come pre-locked? Neither of those seem good, now that I think about it. If they come pre-locked, then you really can't clone, change one property, and return the new one (as is the standard practice now in that case). If they don't come pre-locked, then the newly created object can have everything on it changed, once, which creates a loophole. I'm not sure what the right answer is here. My other concern is a public property (the most likely use case) would have to be set in the constructor. If it's not, then callers cannot rely on it having been set yet if it's set lazily. And if code inside the class tries to set it lazily, it may already have been set by some external code (rightly or wrongly) and cause a failure. How do we address that? There's absolutely use cases where setting everything in the constructor ahead of time is what you'd do anyway, but there are plenty where you wouldn't want to, either, which creates a race condition for who sets it first, or tries to access it before it gets set, etc. (This is where my repeated questions about lazy initialization come from.) --Larry Garfield
  108717
February 22, 2020 00:43 andreas@dqxtech.net (Andreas Hennings)
When writing immutable classes, I want to be able to set properties in
static factories and in wither methods.

Once the new instance is sent to the outside world, its properties can be
locked to prevent further modification.

This sounds to me like we need different modes. Either the object itself
would have different states over time, or the object stays the same and
instead some methods have mutation permission on newly created objects.

This could be seen as a runtime state problem or as a compile time code
verification problem.

On Sat, 22 Feb 2020, 00:18 Larry Garfield, <larry@garfieldtech.com> wrote:

> On Fri, Feb 21, 2020, at 4:29 AM, Máté Kocsis wrote: > > > > > > Yeah, I'm definitely thinking in relation to the earlier discussion, > since > > > I think they're all inter-related. (This, property accessors, and > constant > > > expressions.) > > > > > > > The biggest question is whether it's worth to support both readonly > > properties and property accessors. My answer is clear yes, because there > > are many-many > > ways to mess with private or protected properties without and public > > setters from the outside - in which case property accessors couldn't help > > much. I collected some examples I know of: > > https://3v4l.org/Ta4PM > > I didn't even know you could do some of those. That's horrifying. :-) > > > > As Nikita notes above, a read-only property with a default value is.... > > > basically a constant already. So that's not really useful. > > > > I agree that they are not very useful, however I wouldn't restrict their > > usage. Mainly because there are probably some legitimate use-cases, but I > > also think it would > > be advantageous to be consistent with the other languages in this case. > > If they could do something that class constants can't, that would make > them useful. If not, then I feel like it would just be introducing new > syntax for the same thing, without much benefit. (I'm thinking of, eg, > could you set them by default to a new Foo() object, which you could then > modify the Foo but not change it for another object, thus moving that > initialization out of the constructor? That sort of thing.) > > > > If we could address the performance impact, that would give us much > more > > > functionality-for-the-buck, including an equivalent of read-only > properties > > > including potentially lazy initialization. Or derive-on-demand > behavior > > > would also be a big increase in functionality. > > > > > > It's not that I don't see a value to this RFC; I actually have a few > > > places in my own code where I could use it. It's that I see it as > being of > > > fairly narrow use, so I'm trying to figure out how to increase it so > that > > > the net-win is worth it. > > > > > > > The reason why I brought up this RFC is that I'd really like to add > > first-class support for immutable objects, and it seemed to be a good > idea > > to first go for readonly properties. > > This way, the scope of an immutable object RFC gets smaller, while it's > > possible to only have readonly properties alone. > > > > Regards, > > Máté > > I'm totally on board for better value object support, so that's a good > motive for me. The question I have is whether this is really a good > stepping stone in that direction or if it would lead down a wrong path and > lock us into too much TIMTOWTDI (for the Perl fans in the room). So let's > think that through down that path. How would write-once properties lead > into properly immutable value objects? Or do they give us that themselves? > > The biggest challenge for immutable objects, IMO, is evolving them. Eg, > $result->withContentType(...) to use the PSR-7 example. Would we expect > people to do it with a method like that, or would there be some other > mechanism? If the properties are public, would we offer a more syntactic > way to modify them directly? > > The with*() method style requires cloning the object. What happens to the > locked status of a set property if the object is cloned? Are they then > settable again, or do they come pre-locked? > > Neither of those seem good, now that I think about it. If they come > pre-locked, then you really can't clone, change one property, and return > the new one (as is the standard practice now in that case). If they don't > come pre-locked, then the newly created object can have everything on it > changed, once, which creates a loophole. I'm not sure what the right > answer is here. > > My other concern is a public property (the most likely use case) would > have to be set in the constructor. If it's not, then callers cannot rely > on it having been set yet if it's set lazily. And if code inside the class > tries to set it lazily, it may already have been set by some external code > (rightly or wrongly) and cause a failure. > > How do we address that? There's absolutely use cases where setting > everything in the constructor ahead of time is what you'd do anyway, but > there are plenty where you wouldn't want to, either, which creates a race > condition for who sets it first, or tries to access it before it gets set, > etc. (This is where my repeated questions about lazy initialization come > from.) > > --Larry Garfield > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: http://www.php.net/unsub.php > >
  108725
February 23, 2020 08:39 mike@newclarity.net (Mike Schinkel)
> On Feb 21, 2020, at 6:17 PM, Larry Garfield <larry@garfieldtech.com> wrote: > I'm totally on board for better value object support, so that's a good motive for me. The question I have is whether this is really a good stepping stone in that direction or if it would lead down a wrong path and lock us into too much TIMTOWTDI (for the Perl fans in the room). So let's think that through down that path. How would write-once properties lead into properly immutable value objects? Or do they give us that themselves? > > The biggest challenge for immutable objects, IMO, is evolving them. Eg, $result->withContentType(...) to use the PSR-7 example. Would we expect people to do it with a method like that, or would there be some other mechanism? If the properties are public, would we offer a more syntactic way to modify them directly? > > The with*() method style requires cloning the object. What happens to the locked status of a set property if the object is cloned? Are they then settable again, or do they come pre-locked? > > Neither of those seem good, now that I think about it. If they come pre-locked, then you really can't clone, change one property, and return the new one (as is the standard practice now in that case). If they don't come pre-locked, then the newly created object can have everything on it changed, once, which creates a loophole. I'm not sure what the right answer is here. > > My other concern is a public property (the most likely use case) would have to be set in the constructor. If it's not, then callers cannot rely on it having been set yet if it's set lazily. And if code inside the class tries to set it lazily, it may already have been set by some external code (rightly or wrongly) and cause a failure.
> > How do we address that? There's absolutely use cases where setting everything in the constructor ahead of time is what you'd do anyway, but there are plenty where you wouldn't want to, either, which creates a race condition for who sets it first, or tries to access it before it gets set, etc. (This is where my repeated questions about lazy initialization come from.)
I have struggled to follow this RFC thread fully, so if I am getting something out of context, please note that and I apologize in advance. However, it would see that rules for `write once` properties to support lazy loading would be rather simple: 1. Write-once properties can only be updated once. 2. Write-once properties can only be updated within the class where they are declared. 3. If you want to update a property from outside the class, create a set_() method to allow it to happen. 4. If you do not want it to be set externally, do not implement a set_() method. 5. If you want it to be implemented externally sometimes but not others, implement guard classes inside the set_() method. I think that addresses all scenarios, no? -Mike
  108731
February 23, 2020 17:06 larry@garfieldtech.com ("Larry Garfield")
On Sun, Feb 23, 2020, at 2:39 AM, Mike Schinkel wrote:
> > On Feb 21, 2020, at 6:17 PM, Larry Garfield <larry@garfieldtech.com> wrote: > > I'm totally on board for better value object support, so that's a good motive for me. The question I have is whether this is really a good stepping stone in that direction or if it would lead down a wrong path and lock us into too much TIMTOWTDI (for the Perl fans in the room). So let's think that through down that path. How would write-once properties lead into properly immutable value objects? Or do they give us that themselves? > > > > The biggest challenge for immutable objects, IMO, is evolving them. Eg, $result->withContentType(...) to use the PSR-7 example. Would we expect people to do it with a method like that, or would there be some other mechanism? If the properties are public, would we offer a more syntactic way to modify them directly? > > > > The with*() method style requires cloning the object. What happens to the locked status of a set property if the object is cloned? Are they then settable again, or do they come pre-locked? > > > > Neither of those seem good, now that I think about it. If they come pre-locked, then you really can't clone, change one property, and return the new one (as is the standard practice now in that case). If they don't come pre-locked, then the newly created object can have everything on it changed, once, which creates a loophole. I'm not sure what the right answer is here. > > > > My other concern is a public property (the most likely use case) would have to be set in the constructor. If it's not, then callers cannot rely on it having been set yet if it's set lazily. And if code inside the class tries to set it lazily, it may already have been set by some external code (rightly or wrongly) and cause a failure. > > > > > How do we address that? There's absolutely use cases where setting everything in the constructor ahead of time is what you'd do anyway, but there are plenty where you wouldn't want to, either, which creates a race condition for who sets it first, or tries to access it before it gets set, etc. (This is where my repeated questions about lazy initialization come from.) > > > I have struggled to follow this RFC thread fully, so if I am getting > something out of context, please note that and I apologize in advance. > > However, it would see that rules for `write once` properties to support > lazy loading would be rather simple: > > 1. Write-once properties can only be updated once. > 2. Write-once properties can only be updated within the class where > they are declared.
This is the common use case I think many envision, but nothing in the proposal requires that. A public write-once property (as currently written) would be world-readable, and world-writeable, once. Separate visibility for internal and external access is a separate matter. (Also potentially useful, but not part of the write-once proposal at the moment.)
> 3. If you want to update a property from outside the class, create a > set_() method to allow it to happen. > 4. If you do not want it to be set externally, do not implement a > set_() method. > 5. If you want it to be implemented externally sometimes but not > others, implement guard classes inside the set_() method. > > I think that addresses all scenarios, no? > > -Mike
It does not. 1) Race condition if I assume that a public write-once property is a materialized value, but access it before it gets materialized. 2) Race condition if internal non-constructor code wants to set the value, but some external routine has set it first. 3) Cloning creates an interesting and complicated case of both of the above. Does a cloned object start with its write-once bits reset or no? There's problems both ways. Making a write-once property implicitly write-only-from-inside-the-class would help address point 2, but not points 1 or 3. Adding separate get/set visibility modifiers is another interesting idea, but is separate and should be evaluated on its own merits. It is not at this time part of this proposal. --Larry Garfield
  108733
February 24, 2020 03:26 ocramius@gmail.com (Marco Pivetta)
Hey Larry,



On Sun, Feb 23, 2020, 18:06 Larry Garfield <larry@garfieldtech.com> wrote:

> > It does not. > > 1) Race condition if I assume that a public write-once property is a > materialized value, but access it before it gets materialized. >
This is alrey true for any public typed properties without default values, and why static analysis tools pick up missing initialisation since ages.
> 2) Race condition if internal non-constructor code wants to set the value, > but some external routine has set it first. >
Internal ctors are part of the category above: tools like psalm pick this up as well.
> 3) Cloning creates an interesting and complicated case of both of the > above. Does a cloned object start with its write-once bits reset or no? > There's problems both ways. >
Not a problem: we seem to be making a problem out of the pattern used in current PSR-7 implementations, which is following: final class Foo { public function withBar($bar):self { $instance = clone $this; $instance->foo = $foo; return $instance; } } The solution is trivial: don't use cloning: final class Foo { public function withBar($bar):self { $instance = new self(); $instance->foo = $foo; // more assignments here - unavoidable return $instance; } }
  108734
February 24, 2020 08:18 rowan.collins@gmail.com (Rowan Tommins)
On 24 February 2020 03:26:19 GMT+00:00, Marco Pivetta <ocramius@gmail.com> wrote:
>The solution is trivial: don't use cloning: > >final class Foo >{ > public function withBar($bar):self { > $instance = new self(); > $instance->foo = $foo; > // more assignments here - unavoidable > return $instance; > } >}
This works fine on paper, but is completely impractical as the class grows. Consider a class with 10 such properties, each with their own with* method: switching from clone to explicit assignments means adding 90 lines of code, all of it copy-and-pasted boilerplate that's hard to spot mistakes in. It's also impossible to use with inheritance, or to compose with traits (as Diactoros does, for instance), because every with* method needs to know the full details of how to create a partial clone. Regards, -- Rowan Tommins [IMSoP]
  108735
February 24, 2020 08:25 ocramius@gmail.com (Marco Pivetta)
On Mon, Feb 24, 2020, 09:19 Rowan Tommins collins@gmail.com> wrote:

> On 24 February 2020 03:26:19 GMT+00:00, Marco Pivetta <ocramius@gmail.com> > wrote: > >The solution is trivial: don't use cloning: > > > >final class Foo > >{ > > public function withBar($bar):self { > > $instance = new self(); > > $instance->foo = $foo; > > // more assignments here - unavoidable > > return $instance; > > } > >} > > > This works fine on paper, but is completely impractical as the class > grows. Consider a class with 10 such properties
Yes, considered it: it's fine. Also, the best approach would be to have a ctor with those 10 properties. It's also impossible to use with inheritance, or to compose with traits (as
> Diactoros does, for instance)
Inheritance would be ditched in favour of even more composition, but even then, inheritance is not a massive issue, especially when it comes to value types. Probably a couple hours of work to make a full PSR-7 implementation with the old tests running, and the new approach applied 👍 For things that are more sensible, like a UUID, a Domain Event or a Command, this is really a game changer anyway. The good old Object Calisthenics rules do really apply.
  108744
February 24, 2020 21:25 mike@newclarity.net (Mike Schinkel)
> On Feb 23, 2020, at 12:06 PM, Larry Garfield <larry@garfieldtech.com> wrote: > > On Sun, Feb 23, 2020, at 2:39 AM, Mike Schinkel wrote: >>> On Feb 21, 2020, at 6:17 PM, Larry Garfield <larry@garfieldtech.com> wrote: >>> I'm totally on board for better value object support, so that's a good motive for me. The question I have is whether this is really a good stepping stone in that direction or if it would lead down a wrong path and lock us into too much TIMTOWTDI (for the Perl fans in the room). So let's think that through down that path. How would write-once properties lead into properly immutable value objects? Or do they give us that themselves? >>> >>> The biggest challenge for immutable objects, IMO, is evolving them. Eg, $result->withContentType(...) to use the PSR-7 example. Would we expect people to do it with a method like that, or would there be some other mechanism? If the properties are public, would we offer a more syntactic way to modify them directly? >>> >>> The with*() method style requires cloning the object. What happens to the locked status of a set property if the object is cloned? Are they then settable again, or do they come pre-locked? >>> >>> Neither of those seem good, now that I think about it. If they come pre-locked, then you really can't clone, change one property, and return the new one (as is the standard practice now in that case). If they don't come pre-locked, then the newly created object can have everything on it changed, once, which creates a loophole. I'm not sure what the right answer is here. >>> >>> My other concern is a public property (the most likely use case) would have to be set in the constructor. If it's not, then callers cannot rely on it having been set yet if it's set lazily. And if code inside the class tries to set it lazily, it may already have been set by some external code (rightly or wrongly) and cause a failure. >> >>> >>> How do we address that? There's absolutely use cases where setting everything in the constructor ahead of time is what you'd do anyway, but there are plenty where you wouldn't want to, either, which creates a race condition for who sets it first, or tries to access it before it gets set, etc. (This is where my repeated questions about lazy initialization come from.) >> >> >> I have struggled to follow this RFC thread fully, so if I am getting >> something out of context, please note that and I apologize in advance. >> >> However, it would see that rules for `write once` properties to support >> lazy loading would be rather simple: >> >> 1. Write-once properties can only be updated once. >> 2. Write-once properties can only be updated within the class where >> they are declared. > > This is the common use case I think many envision, but nothing in the proposal requires that. A public write-once property (as currently written) would be world-readable, and world-writeable, once. > Separate visibility for internal and external access is a separate matter. (Also potentially useful, but not part of the write-once proposal at the moment.)
This just hit me, so I think I will mention it. The culture on the list seems not to be one of collaboration on RFC to find solutions to improve PHP but instead waiting for someone to stick their neck out with an RFC and then see who can shoot the most holes through it. I did not actually expect that. I would have hoped for a collaborate culture of the nature I am used to in local startup-oriented meetups where most everyone is trying to help each other rather than just pointing out that the work someone else has done is deficient and not worthy of moving forward. If that is the desired culture that most want to have on this list, then I will accept it even though I will lament how much better it could be otherwise. If I misunderstand, please help me understand how wording like "but nothing in the proposal requires that" and "not part of the write-once proposal at the moment" rather than "the proposal does not require that but lets's explore how adding that requirement can make the proposal better" should not leave the impression I just mentioned above? Or maybe the problem is the mailing list is just not a mechanism for collaborative work and we need a new mechanism. Like Github, comments, and pull requests? Moving on...
>> 3. If you want to update a property from outside the class, create a >> set_() method to allow it to happen. >> 4. If you do not want it to be set externally, do not implement a >> set_() method. >> 5. If you want it to be implemented externally sometimes but not >> others, implement guard classes inside the set_() method. >> >> I think that addresses all scenarios, no? >> >> -Mike > > It does not. > > 1) Race condition if I assume that a public write-once property is a materialized value, but access it before it gets materialized.
Can you please explain what "materialized value" means in this context? Are you speaking in Scala again? If you mean that a write-once property just has not yet been assigned, I am not sure how that differs from just accessing a property that has not yet been assigned today?
> 2) Race condition if internal non-constructor code wants to set the value, but some external routine has set it first.
As I said, don't let the external routine set it directly. Which you did note. (I am interested in finding solutions in the same solution space as selected proposals and not just limiting myself to the exact specifics of the proposal because IMO limiting ourselves in that way has all the bad aspects of bureaucracy and none of the good aspects.)
> 3) Cloning creates an interesting and complicated case of both of the above. Does a cloned object start with its write-once bits reset or no? There's problems both ways.
If there are problems, let us find solutions. 1. Only allow internal methods to change write-once properties. 2. Options: 2a. Extend clone statement to have `clone rewrite $foo` that would allow rewriting the write-once properties. 2b. Allow rewriting once of write-once object properties until some operation is performed to seal or lock the object (operation being a method call, a function call with the object as param, or a statement with the object as argument.) 2c. Don't allow rewriting of cloned objects. See if it is really a problem. Fix it in a future RFC if so. 2d. Something else I am someone we can think of. -Mike
  108750
February 25, 2020 04:45 larry@garfieldtech.com ("Larry Garfield")
On Mon, Feb 24, 2020, at 3:25 PM, Mike Schinkel wrote:
> > On Feb 23, 2020, at 12:06 PM, Larry Garfield <larry@garfieldtech.com> wrote: > > > > On Sun, Feb 23, 2020, at 2:39 AM, Mike Schinkel wrote: > >>> On Feb 21, 2020, at 6:17 PM, Larry Garfield <larry@garfieldtech.com> wrote: > >>> I'm totally on board for better value object support, so that's a good motive for me. The question I have is whether this is really a good stepping stone in that direction or if it would lead down a wrong path and lock us into too much TIMTOWTDI (for the Perl fans in the room). So let's think that through down that path. How would write-once properties lead into properly immutable value objects? Or do they give us that themselves? > >>> > >>> The biggest challenge for immutable objects, IMO, is evolving them.. Eg, $result->withContentType(...) to use the PSR-7 example. Would we expect people to do it with a method like that, or would there be some other mechanism? If the properties are public, would we offer a more syntactic way to modify them directly? > >>> > >>> The with*() method style requires cloning the object. What happens to the locked status of a set property if the object is cloned? Are they then settable again, or do they come pre-locked? > >>> > >>> Neither of those seem good, now that I think about it. If they come pre-locked, then you really can't clone, change one property, and return the new one (as is the standard practice now in that case). If they don't come pre-locked, then the newly created object can have everything on it changed, once, which creates a loophole. I'm not sure what the right answer is here. > >>> > >>> My other concern is a public property (the most likely use case) would have to be set in the constructor. If it's not, then callers cannot rely on it having been set yet if it's set lazily. And if code inside the class tries to set it lazily, it may already have been set by some external code (rightly or wrongly) and cause a failure. > >> > >>> > >>> How do we address that? There's absolutely use cases where setting everything in the constructor ahead of time is what you'd do anyway, but there are plenty where you wouldn't want to, either, which creates a race condition for who sets it first, or tries to access it before it gets set, etc. (This is where my repeated questions about lazy initialization come from.) > >> > >> > >> I have struggled to follow this RFC thread fully, so if I am getting > >> something out of context, please note that and I apologize in advance. > >> > >> However, it would see that rules for `write once` properties to support > >> lazy loading would be rather simple: > >> > >> 1. Write-once properties can only be updated once. > >> 2. Write-once properties can only be updated within the class where > >> they are declared. > > > > This is the common use case I think many envision, but nothing in the proposal requires that. A public write-once property (as currently written) would be world-readable, and world-writeable, once. > > Separate visibility for internal and external access is a separate matter. (Also potentially useful, but not part of the write-once proposal at the moment.) > > This just hit me, so I think I will mention it. > > The culture on the list seems not to be one of collaboration on RFC to > find solutions to improve PHP but instead waiting for someone to stick > their neck out with an RFC and then see who can shoot the most holes > through it. > > I did not actually expect that. I would have hoped for a collaborate > culture of the nature I am used to in local startup-oriented meetups > where most everyone is trying to help each other rather than just > pointing out that the work someone else has done is deficient and not > worthy of moving forward.
Mike, Terje already responded here and he is spot on: "Destructive testing" is a very good metaphor to use, and is what is happening in this thread. Evolving a language like PHP, with millions of users and billions of lines of code, is not, even a little, like a startup-oriented meetup. In a meetup, "yes and" is a perfectly good model, and you can encourage everyone to make their own thing, throw it out into the market, and see what happens. Multiple incompatible and competing startups can succeed and the ecosystem is better for it. But there is only one PHP language, and throwing everything and the kitchen sink into it just to make people feel good and inspired is a superb way to ruin the language. *Every* new feature or syntax addition creates cognitive overhead for *millions* of people. If done in a way that has lots of holes or flaws, it can create billion-dollar bugs that take a decade to unravel, if ever. The cost of "getting it wrong" is high. The cost/benefit analysis has to be very strong to justify adding syntactic weight to every PHP user in the world. That does not mean that we should oppose all change; hell, PHP is perhaps the fastest evolving production language in the world right now. There's a long list of recent features I've supported and cheered for, and a long list I'd still like to see. The same is true of almost everyone on this list. But in order to keep PHP a good, approachable, flexible language and keep it consistent (well, at least no more inconsistent than it already is), every one of those features needs to "run the gauntlet" of destructive testing, poking holes in it, and finding all the gotchas. That's exactly the point: We *do* want to find all the little gotchas in a proposal, now, before something gets into the language and the PHP-coding world finds them instead. Constructive nitpicking and destructive testing, in this context, are a sign of respect. If folks on the list didn't respect this proposal, it would have been met with "this is stupid", "we went over this, it's dumb", "why are you wasting our time?", or other such responses. (Certainly there have been proposals met with such response in the past.) Or, simply ignored outright. Instead, it's being met with "Hm, someone is going to try and throw this against a wall harder than they have any right to; so let's do it now and see what happens so we can figure out how to make it not shatter into a thousand pieces." That's a good thing. Numerous people are donating a not-small amount of their time to stress-test the proposal. Some of that work has already improved the proposal, eg, the general agreement that it was best to just ignore untyped properties and references entirely. That's a good thing. Máté has said that his end goal is immutable objects, and this is a step in that direction. I fully support that goal. Is this actually the right first step in that direction, or does it go down a wrong path? I don't know yet; that's what all of the nitpicking and hypotheticals and "running it over with a car to see what happens" is about: Figuring out if it is the right way to get to that goal. I'm glad we're having this conversation. Maybe a readonly flag only makes sense if we first/also add separate visibility for getters and setters? I don't know, but it's worth asking. Does it break cloning? Seems like it, but maybe there's a way around it.. Would some totally different approach get to a better end-state with fewer edge cases? Could be. Those are realizations that only happen when you take an RFC and "shoot the most holes through it", because then you find the places where it's not bulletproof. That's the process working as designed.
> > 1) Race condition if I assume that a public write-once property is a materialized value, but access it before it gets materialized. > > Can you please explain what "materialized value" means in this context? > Are you speaking in Scala again?
I don't speak Scala. If anything it's an SQL reference. A "materialized table" is basically a saved query result, which you can then reuse over and over again, that self-updates when its underlying source changes. Think $user->fullName, where fullName is a public read-only property derived by concatenating $firstName and $lastName. If that is set in the User constructor, great, no problem. If it's not, because it's not always needed so you want to only do that work if it's going to be used, then the caller doesn't know if it's already been set or if it's still undefined. Or, if public write isn't blanket-disallowed (which could be done but I suspect would lead to other limitations), whatever internal code would set the value doesn't know if it's already been set by an over-eager external routine (possibly incorrectly).
> If you mean that a write-once property just has not yet been assigned, > I am not sure how that differs from just accessing a property that has > not yet been assigned today?
It doesn't, which is one of the reasons public properties are broadly frowned upon today. However, most of the use cases of a readonly flag I can see apply to publicly-readable properties (which are therefore faster to access than a method and involve less boilerplate code). So if a readonly flag doesn't resolve that problem, and in practice makes it a bit worse, that puts it at a net-negative ROI. I keep coming back to supporting lazy-initialization because that seems to me the best way to resolve that problem, and gives us some very clean additional functionality. Are there other options that are better? Could well be. Let's enumerate them and poke holes in all of those, too.
> > 3) Cloning creates an interesting and complicated case of both of the above. Does a cloned object start with its write-once bits reset or no? There's problems both ways. > > If there are problems, let us find solutions. > > 1. Only allow internal methods to change write-once properties.
Possible; which then leads to the question "why can't every property have asymmetric access?" Which someone else upthread mentioned, and IMO warrants its own discussion.
> 2. Options: > 2a. Extend clone statement to have `clone rewrite $foo` that would > allow rewriting the write-once properties.
In the common case, you'd only want to overwrite a subset of properties, probably only one. How do you "unlock" just that one property? Is that the right way to go about it? (I honestly don't know.)
> 2b. Allow rewriting once of write-once object properties until some > operation is performed to seal or lock the object (operation being a > method call, a function call with the object as param, or a statement > with the object as argument.)
My preference here would be the "overwrite code block" Rowan suggested. That may have other knock-on effects (either for consistency or engine complexity) that we haven't thought of yet.
> 2c. Don't allow rewriting of cloned objects. See if it is really a > problem. Fix it in a future RFC if so.
Possible, but also then removes one of the primary use cases for the feature: Immutable value objects, since then evolving them becomes far more work than it is now. --Larry Garfield
  108751
February 25, 2020 09:04 kocsismate90@gmail.com (=?UTF-8?B?TcOhdMOpIEtvY3Npcw==?=)
My original idea was to discuss cloning in connection with immutable
objects, - I think it's only a serious problem in that case - but we can
bring this discussion earlier of course, as readonly properties certainly
has some effect on the clonability of objects.

When I started working on implementing immutable objects, I tried to come
up with a solution for the problem of property mutation. Actually, I even
had a short discussion with Nikita about the topic. He proposed the
following syntax (which is inspired by Rust):

public function withFoo(FooT $foo): static {
    return new static { foo => $foo, ...$this };
}

My idea was very similar, but it affects cloning:

$self = clone $this with { foo => $foo, ... }

I also tried to experiment with similar solutions to the ones proposed by
Rowan, where readonly properties were unsealed after cloning (in certain
circumstances), but my general feeling was these are not the way to go. To
be honest, I also don't really like object initializers because they omit
invoking the constructor. However, "beefing up" cloning would make sense
for me.

Given, adding support for object initializers (or a special for of it), is
also a non-trivial problem - as seen in case of the previous RFC -, and
given, the absence of this construct is not a deal-breaker for readonly
properties, I still think that it would be worth to separate the two topic
(and vote separately). This way, we can keep the current proposal
self-contained and focused. If the current vote passed, I'd certainly try
to address the problem of property mutation after cloning in the immutable
object proposal.

Regards,
Máté
  108752
February 25, 2020 11:05 kocsismate90@gmail.com (=?UTF-8?B?TcOhdMOpIEtvY3Npcw==?=)
Sorry, but I'd like to add a correction to my previous email.

After thinking about it, I realized that cloning is not that serious of a
problem in case of immutable objects, since it's also possible to create a
new instance instead of cloning. So it seems to be only a Developer
Experience concern for me - as cloning is more comfortable to use than
passing all the properties to a new object.

Regards,
Máté
  108736
February 24, 2020 13:55 rowan.collins@gmail.com (Rowan Tommins)
On Fri, 21 Feb 2020 at 23:18, Larry Garfield <larry@garfieldtech.com> wrote:

> The with*() method style requires cloning the object. What happens to the > locked status of a set property if the object is cloned? Are they then > settable again, or do they come pre-locked? > > Neither of those seem good, now that I think about it. If they come > pre-locked, then you really can't clone, change one property, and return > the new one (as is the standard practice now in that case). If they don't > come pre-locked, then the newly created object can have everything on it > changed, once, which creates a loophole. I'm not sure what the right > answer is here. >
As with typed properties, I wonder if there's a way we can introduce a new initialisation sequence for objects, so that there's a specific point where the object is considered "fully constructed" after new or clone. A couple of brainstormed ideas, with plenty of downsides I'm sure: An explicit finalise() function or keyword public function withFoo($foo) { $inst = clone $this; // all readonly properties are initially "unlocked" $inst->foo = $foo; // now lock them, perhaps also checking that no typed properties are left uninitialised finalise($inst); // or finalise $inst; return $inst; } A special code block: public function withFoo($foo) { $inst = clone $this { // all properties are "unlocked" within this special block $inst->foo = $foo; }; // from here onwards, readonly properties can't be written to return $inst; } Perhaps could also be used with constructors: public function createFromOtherThing(OtherThing $other) { $inst = new static('some parameter') { // readonly properties can be written in the constructor, or within this block $inst->foo = $other->getFoo(); }; // object is "finalised" when the block ends return $inst; } Regards, -- Rowan Tommins [IMSoP]
  108743
February 24, 2020 20:35 larry@garfieldtech.com ("Larry Garfield")
On Mon, Feb 24, 2020, at 7:55 AM, Rowan Tommins wrote:
> On Fri, 21 Feb 2020 at 23:18, Larry Garfield <larry@garfieldtech.com> wrote: > > > The with*() method style requires cloning the object. What happens to the > > locked status of a set property if the object is cloned? Are they then > > settable again, or do they come pre-locked? > > > > Neither of those seem good, now that I think about it. If they come > > pre-locked, then you really can't clone, change one property, and return > > the new one (as is the standard practice now in that case). If they don't > > come pre-locked, then the newly created object can have everything on it > > changed, once, which creates a loophole. I'm not sure what the right > > answer is here. > > > > > As with typed properties, I wonder if there's a way we can introduce a new > initialisation sequence for objects, so that there's a specific point where > the object is considered "fully constructed" after new or clone. > > A couple of brainstormed ideas, with plenty of downsides I'm sure: > > An explicit finalise() function or keyword > > public function withFoo($foo) { > $inst = clone $this; > // all readonly properties are initially "unlocked" > $inst->foo = $foo; > // now lock them, perhaps also checking that no typed properties are > left uninitialised > finalise($inst); // or finalise $inst; > return $inst; > } > > A special code block: > > public function withFoo($foo) { > $inst = clone $this { > // all properties are "unlocked" within this special block > $inst->foo = $foo; > }; > // from here onwards, readonly properties can't be written to > return $inst; > } > > Perhaps could also be used with constructors: > > public function createFromOtherThing(OtherThing $other) { > $inst = new static('some parameter') { > // readonly properties can be written in the constructor, or > within this block > $inst->foo = $other->getFoo(); > }; > // object is "finalised" when the block ends > return $inst; > }
If the way to resolve this question is a special "unlocked" mode, I would definitely favor the explicit code block. That way it's self-closing and you can't forget to do so. (Murphy's Law: If you rely on developers remembering to do X to keep code safe, they will promptly forget to do X.) Also, FTR, any approach that forces developers to write a 9 parameter constructor over and over is one I can never get behind, doubly so for something that is currently only a single line. This isn't a case of "pain tells you what not to do"; a value object having a lot of internal properties is a completely valid use case, and "but composition" is not an answer. --Larry Garfield
  108753
February 25, 2020 11:24 nicolas.grekas+php@gmail.com (Nicolas Grekas)
Le lun. 24 févr. 2020 à 21:35, Larry Garfield <larry@garfieldtech..com> a
écrit :

> On Mon, Feb 24, 2020, at 7:55 AM, Rowan Tommins wrote: > > On Fri, 21 Feb 2020 at 23:18, Larry Garfield <larry@garfieldtech.com> > wrote: > > > > > The with*() method style requires cloning the object. What happens to > the > > > locked status of a set property if the object is cloned? Are they then > > > settable again, or do they come pre-locked? > > > > > > Neither of those seem good, now that I think about it. If they come > > > pre-locked, then you really can't clone, change one property, and > return > > > the new one (as is the standard practice now in that case). If they > don't > > > come pre-locked, then the newly created object can have everything on > it > > > changed, once, which creates a loophole. I'm not sure what the right > > > answer is here. > > > > > > > > > As with typed properties, I wonder if there's a way we can introduce a > new > > initialisation sequence for objects, so that there's a specific point > where > > the object is considered "fully constructed" after new or clone. > > > > A couple of brainstormed ideas, with plenty of downsides I'm sure: > > > > An explicit finalise() function or keyword > > > > public function withFoo($foo) { > > $inst = clone $this; > > // all readonly properties are initially "unlocked" > > $inst->foo = $foo; > > // now lock them, perhaps also checking that no typed properties are > > left uninitialised > > finalise($inst); // or finalise $inst; > > return $inst; > > } > > > > A special code block: > > > > public function withFoo($foo) { > > $inst = clone $this { > > // all properties are "unlocked" within this special block > > $inst->foo = $foo; > > }; > > // from here onwards, readonly properties can't be written to > > return $inst; > > } > > > > Perhaps could also be used with constructors: > > > > public function createFromOtherThing(OtherThing $other) { > > $inst = new static('some parameter') { > > // readonly properties can be written in the constructor, or > > within this block > > $inst->foo = $other->getFoo(); > > }; > > // object is "finalised" when the block ends > > return $inst; > > } > > If the way to resolve this question is a special "unlocked" mode, I would > definitely favor the explicit code block. That way it's self-closing and > you can't forget to do so. (Murphy's Law: If you rely on developers > remembering to do X to keep code safe, they will promptly forget to do X.) > > Also, FTR, any approach that forces developers to write a 9 parameter > constructor over and over is one I can never get behind, doubly so for > something that is currently only a single line. This isn't a case of "pain > tells you what not to do"; a value object having a lot of internal > properties is a completely valid use case, and "but composition" is not an > answer.
I totally agree with this: there must be a way to work around the keyword - either via reflection or another means. Unlike `private`, `final` is an imperative keyword that cannot be bypassed, while there are very legit use cases for still extending final classes (proxies). For sure, the same will be legitimately needed with `readonly`. Via Reflection, it could be a new method `->setWritable(true)` (next to `->setAccessible(true)`). Another way, which is my current preference, would be to have visibility be taken into consideration when using the keyword: - `public readonly $foo` => cannot be set from the outside of the class - but can be from protected+private scopes - `protected readonly $foo` => can be set only from private scope - `private readonly $foo` => either unsupported or have the `writeonce` behavior defined in the RFC. This would make the keyword compatible with untyped properties and with cloning. It would provide the guarantee we need from an author pov: outside scopes cannot mess up with the state of properties. It would not provide authors a safeguard against their own mistakes - but I think that's a really secondary goal and one that is fine letting go vs the mentioned benefits. Nicolas
  108862
March 5, 2020 10:57 kocsismate90@gmail.com (=?UTF-8?B?TcOhdMOpIEtvY3Npcw==?=)
> > I totally agree with this: there must be a way to work around the keyword - > either via reflection or another means.
Via Reflection, it could be a new method `->setWritable(true)` (next to
> `->setAccessible(true)`). >
I'm OK to support working around the keyword via reflection. Since my implementation uses a property flag to determine if a property is writable, and the flag is flipped after writing, the ->setWritable(true) wouldn't work. But I can imagine adding a ->setUninitialized() method, or adding a third parameter to ->setValue() which would affect whether the property flag should be first reset or not. Adding a separate ->setValue() method for this purpose is also possible, but I couldn't find a good name for that yet. What do you think?
> Another way, which is my current preference, would be to have visibility be > taken into consideration when using the keyword: >
To be honest, I don't agree with this idea. It is rather a simpler property accessor variant with which one can define separate - but predefined - visibility rules for reading and writing. Furthermore, I think it's worth to protect people from their own mistakes. I know for sure that at least I'd need those safeguards. :) But if we are talking about unintended changes coming from outside scopes, and if we only consider "legal" use-cases, private and protected properties are already pretty much safe. However, if we also consider possible changes coming from an inside scope, and other, "illegal" use-cases, we currently don't have any protection. And not even property accessors or your idea could help here. Given, immutability became mainstream via PSR-7 at least, I think it would be beneficial to add first-class support for this principle. I remember that one of the criticisms against the implementation of PSR-7 was that immutability is not even possible to achieve in PHP (apart from the fact that PSR-7 uses a mutable resource for storing the body). Now, we could solve the first problem, but that requires us to stay with the proposed implementation of the RFC. Regards, Máté
  108892
March 7, 2020 21:25 kocsismate90@gmail.com (=?UTF-8?B?TcOhdMOpIEtvY3Npcw==?=)
TL;DR; I plan to open the vote on Monday because the proposal feels complete
for me. Read more to find out why I think so.

Nicolas, do you have any specific use-case in mind that would require a
workaround
outlined in my previous email above the current possibilities (lazy
initialization,
unsetting before first write)?

At first, I wasn't entirely sure if we really need additional reflection
methods, but after
a quick chat with Nikita, I'm even less so. Furthermore, it seems that
ProxyManager
will be able to work together with my proposal in its current form, so I
fail to see
what use-case would need more special treatment? Doctrine maybe? Although I
am
not very familiar with it, using
ReflectionClass::newInstanceWithoutConstructor()
could work around the limitations imposed by write-once properties.

That said, I'd like to start the vote on Monday if no major issues emerge
meanwhile,
because the proposal feels complete now and I can't think of any specific
use-case
that would be blocked by write-once properties. Of course, if we yet find
any, we can
always add support for additional reflection-based workarounds later.

Máté
  108995
March 12, 2020 13:57 kocsismate90@gmail.com (=?UTF-8?B?TcOhdMOpIEtvY3Npcw==?=)
As you might noticed, I've not opened the vote yet. Partly because I was
improving
my implementation as well as the RFC itself (added some words about the
inheritance
implications), but the main reason is that a question arise in the
meanwhile.

Namely, "write-once" properties could in principle support covariance. That
is, a subclass
would be allowed to tighten the property type that is inherited from the
parent class.
It would be a slight change compared to regular properties that are
invariant.

All this would be possible because of the quasi-immutable nature of
"write-once" properties:
they are generally expected to be assigned to only once, in the constructor
- which is exempt from
LSP checks.

There is a gotcha though... In practice, "write-once" properties could be
written from places
other than the constructor. Although there might not be many practical
use-cases for it,
the infamous setter injection is certainly one (as shown at
https://3v4l.org/DQ3To), in which
case property covariance would be a problem.

That's why I'm curious about some additional input on the matter. Do you
think
covariance of "write-once" properties is worth to have even though there
might be
some edge-cases when it can't be supported perfectly? I'll include this
topic a bit later
in the RFC as well. In the worst case it could be added to the "Future
Scope" section
because - and correct me if I'm wrong - we can also add support for it
later since it would be
a non-breaking change.

Cheers,
Máté
  108997
March 12, 2020 19:17 larry@garfieldtech.com ("Larry Garfield")
On Thu, Mar 12, 2020, at 8:57 AM, Máté Kocsis wrote:
> As you might noticed, I've not opened the vote yet. Partly because I was > improving > my implementation as well as the RFC itself (added some words about the > inheritance > implications), but the main reason is that a question arise in the > meanwhile. > > Namely, "write-once" properties could in principle support covariance. That > is, a subclass > would be allowed to tighten the property type that is inherited from the > parent class. > It would be a slight change compared to regular properties that are > invariant. > > All this would be possible because of the quasi-immutable nature of > "write-once" properties: > they are generally expected to be assigned to only once, in the constructor > - which is exempt from > LSP checks. > > There is a gotcha though... In practice, "write-once" properties could be > written from places > other than the constructor. Although there might not be many practical > use-cases for it, > the infamous setter injection is certainly one (as shown at > https://3v4l.org/DQ3To), in which > case property covariance would be a problem. > > That's why I'm curious about some additional input on the matter. Do you > think > covariance of "write-once" properties is worth to have even though there > might be > some edge-cases when it can't be supported perfectly? I'll include this > topic a bit later > in the RFC as well. In the worst case it could be added to the "Future > Scope" section > because - and correct me if I'm wrong - we can also add support for it > later since it would be > a non-breaking change. > > Cheers, > Máté
I'd strongly suggest not messing with that for now. There's a definite can of worms, not all of which we likely know about yet. Plus, as you say, it's easier to add support for that later if we can find all of the worms than to try and put those worms back in the box after they've gotten loose. --Larry Garfield
  109009
March 14, 2020 11:00 kocsismate90@gmail.com (=?UTF-8?B?TcOhdMOpIEtvY3Npcw==?=)
Thank you, Larry, for your response! I share your opinion. However, I'd be
curious if there is anyone who doesn't?

As things currently stand, I plan to start the vote on Monday with an
unchanged proposal (+ an extended future scope section).

Máté
  109024
March 14, 2020 21:52 ocramius@gmail.com (Marco Pivetta)
Hey Máté,

Is the RFC still gonna allow default values (constants, at this point)?

While I don't see a major problem with it, it seems a bit weird...

On Sat, Mar 14, 2020, 12:00 Máté Kocsis <kocsismate90@gmail.com> wrote:

> Thank you, Larry, for your response! I share your opinion. However, I'd be > curious if there is anyone who doesn't? > > As things currently stand, I plan to start the vote on Monday with an > unchanged proposal (+ an extended future scope section). > > Máté >
  109025
March 15, 2020 13:04 kocsismate90@gmail.com (=?UTF-8?B?TcOhdMOpIEtvY3Npcw==?=)
Hi Marco,

Yes, it still allows default values.

The reason why I'm reluctant to disallow them is that this restriction
would feel a bit ad-hoc for me. I mean, I wouldn't like to add another
special rule for "write-once" properties, unless there is a strong
argument for it. Besides, as far as I know there is no precedents of
disallowing default values of similar properties in other languages,
so I feel that the feature would stay the most intuitive as it is now.

However, I'm eager to listen any objections about this. I know for one
that ProxyManager wouldn't work with "write-once" properties having
default values. But can we consider this use-case an edge case, right?
Could users circumvent the issue just by changing the default value
to an assignment in the constructor? Or would it cause a big headache
for them?

Máté

Marco Pivetta <ocramius@gmail.com> ezt írta (időpont: 2020. márc. 14., Szo,
22:53):

> Hey Máté, > > Is the RFC still gonna allow default values (constants, at this point)? > > While I don't see a major problem with it, it seems a bit weird... > > On Sat, Mar 14, 2020, 12:00 Máté Kocsis <kocsismate90@gmail.com> wrote: > >> Thank you, Larry, for your response! I share your opinion. However, I'd be >> curious if there is anyone who doesn't? >> >> As things currently stand, I plan to start the vote on Monday with an >> unchanged proposal (+ an extended future scope section). >> >> Máté >> >
  109026
March 15, 2020 13:48 ocramius@gmail.com (Marco Pivetta)
Hey Máté,

On Sun, Mar 15, 2020, 14:04 Máté Kocsis <kocsismate90@gmail.com> wrote:

> Hi Marco, > > Yes, it still allows default values. > > The reason why I'm reluctant to disallow them is that this restriction > would feel a bit ad-hoc for me. I mean, I wouldn't like to add another > special rule for "write-once" properties, unless there is a strong > argument for it. Besides, as far as I know there is no precedents of > disallowing default values of similar properties in other languages, > so I feel that the feature would stay the most intuitive as it is now. >
I think what will happen is that people will start requesting for read-only properties with default values to be over-writable-once (a mess): better to remove them from the equation completely, no?
> However, I'm eager to listen any objections about this. I know for one > that ProxyManager wouldn't work with "write-once" properties having > default values. But can we consider this use-case an edge case, right? >
Yeah, that's totally unrelated to library technicalities: since reflection will tell us if a property is write-once, we can work around it regardless. Could users circumvent the issue just by changing the default value
> to an assignment in the constructor? Or would it cause a big headache > for them? >
I would say that this complicates the semantics more, but you see that the issue is indeed a bit more deep than what we wanted to tackle, so I think a sensible solution is to: 1. Prevent the parser from accepting default values on write-once properties (parser error) 2. Re-introduce them once we know what we want to do with default values (and it makes sense) We can then use the fact that nobody is relying on them to play with the future scope. Nikic suggested (in chat) an example usage such as default non-scalar values as viable: class Application { public read-only DateTimeImmutable $startupTime = new DateTimeImmutable (); // ... }
  109036
March 15, 2020 18:43 larry@garfieldtech.com ("Larry Garfield")
On Sun, Mar 15, 2020, at 8:48 AM, Marco Pivetta wrote:
> Hey Máté, > > On Sun, Mar 15, 2020, 14:04 Máté Kocsis <kocsismate90@gmail.com> wrote: > > > Hi Marco, > > > > Yes, it still allows default values. > > > > The reason why I'm reluctant to disallow them is that this restriction > > would feel a bit ad-hoc for me. I mean, I wouldn't like to add another > > special rule for "write-once" properties, unless there is a strong > > argument for it. Besides, as far as I know there is no precedents of > > disallowing default values of similar properties in other languages, > > so I feel that the feature would stay the most intuitive as it is now. > > > > I think what will happen is that people will start requesting for read-only > properties with default values to be over-writable-once (a mess): better to > remove them from the equation completely, no?
My concern is that if a readonly property can have a default value which is not overwriteable, then it's conceptually isomorphic to a class constant. That will lead to ample questions and debates about whether you should use a class constant or a read-only property, or when you should use one or the other. I foresee numerous bikeshed debates about that, which will only lead to thousands of hours of lost time debating something that shouldn't exist. Avoiding that confusion will save the industry millions of dollars. --Larry Garfield
  109043
March 15, 2020 21:44 kocsismate90@gmail.com (=?UTF-8?B?TcOhdMOpIEtvY3Npcw==?=)
> > Avoiding that confusion will save the industry millions of dollars. >
On the one hand, you are right, because currently it's not very useful to effectively provide two ways of declaring a constant. On the other hand however, if we also consider a longer term aim of adding support for object default values (just like what Marco mentioned): public read-only DateTimeImmutable $startupTime = new DateTimeImmutable(); then allowing default values for "write-once" properties seems much more sensible. At this point, the "million dollar mistake" label doesn't hold anymore since class constants and "write-once" properties with default values will actually be two different things. But we've just ended up at Marco's suggestion:
> 1. Prevent the parser from accepting default values on write-once > properties (parser error) > 2. Re-introduce them once we know what we want to do with default values > (and it makes sense) >
Yes, this scenario definitely makes sense. I'm just not yet sold that it will have any negative effects if we don't restrict the usage of default values now. I understand that it's usually advantageous to be conservative with adding new features - especially when groping in the dark - since we are the ones who have to support and fix them later. That's why I was hesitant to add property covariance to the proposal. But this case seems to be much more well-understood than let's say property covariance would be, it avoids another special rule while aligning nicely with our longer term goals, so I think all in all, it is still a better idea to allow default values than not. I think what will happen is that people will start requesting for read-only
> properties with default values to be
over-writable-once (a mess): better to remove them from the equation
> completely, no?
I can also imagine people to ask for default values if we disallow them now. :) After all, we always want what we don't have. ^^ Does anyone else have any more comments or objections? I wouldn't in the least want to be stubborn about this topic, so if anyone has any feelings for/against, it's the best time to reassure my point of view or change my mind. :) Thanks, Máté
  109045
March 15, 2020 23:22 larry@garfieldtech.com ("Larry Garfield")
On Sun, Mar 15, 2020, at 4:44 PM, Máté Kocsis wrote:
> > > > Avoiding that confusion will save the industry millions of dollars. > > > > On the one hand, you are right, because currently it's not very useful to > effectively provide two > ways of declaring a constant. On the other hand however, if we also > consider a longer term > aim of adding support for object default values (just like what Marco > mentioned): > public read-only DateTimeImmutable $startupTime = new DateTimeImmutable(); > then allowing default values for "write-once" properties seems much more > sensible. At this point, > the "million dollar mistake" label doesn't hold anymore since class > constants and "write-once" > properties with default values will actually be two different things. But > we've just ended up at > Marco's suggestion:
I'll be honest, I really have no idea how default object values is at all related here. I mean, those would be nice to have as well for various reasons, but I don't see how it's related to read-only properties.
> > 1. Prevent the parser from accepting default values on write-once > > properties (parser error) > > 2. Re-introduce them once we know what we want to do with default values > > (and it makes sense) > > Yes, this scenario definitely makes sense. I'm just not yet sold that it > will have any negative effects > if we don't restrict the usage of default values now. I understand that > it's usually advantageous to be > conservative with adding new features - especially when groping in the dark > - since we are the ones > who have to support and fix them later. That's why I was hesitant to add > property covariance to the > proposal.
The negative effect is if we later decide that it's too much of a problem to have default values, because they're just too confusing (people debating if they should be a constant, people expecting them to be overwriteable, etc.), we can't simply remove them. That would be a large breaking change and not allowed, so we're stuck with 'em. Whereas if we don't add it now, we can see if people are really clammoring for it and in what situations. We let the PHP-using masses do the research for us to determine how read-only-default would actually be useful, or if it would be useful at all. Then we can add it later in a way that would actually be useful, or decide not to do it at all. --Larry Garfield
  109052
March 16, 2020 09:51 nikita.ppv@gmail.com (Nikita Popov)
On Mon, Mar 16, 2020 at 12:23 AM Larry Garfield <larry@garfieldtech.com>
wrote:

> On Sun, Mar 15, 2020, at 4:44 PM, Máté Kocsis wrote: > > > > > > Avoiding that confusion will save the industry millions of dollars. > > > > > > > On the one hand, you are right, because currently it's not very useful to > > effectively provide two > > ways of declaring a constant. On the other hand however, if we also > > consider a longer term > > aim of adding support for object default values (just like what Marco > > mentioned): > > public read-only DateTimeImmutable $startupTime = new > DateTimeImmutable(); > > then allowing default values for "write-once" properties seems much more > > sensible. At this point, > > the "million dollar mistake" label doesn't hold anymore since class > > constants and "write-once" > > properties with default values will actually be two different things. But > > we've just ended up at > > Marco's suggestion: > > I'll be honest, I really have no idea how default object values is at all > related here. I mean, those would be nice to have as well for various > reasons, but I don't see how it's related to read-only properties. >
Default object values are a case where default values for readonly properties have a legitimate use-case, because the actual value will be different for each object. Currently only constant values are permissible, which will thus be the same for all objects, and as such not materially different from class constants. A few more considerations on how readonly defaults this would interact with potential future language changes... What happens if we introduce a shorthand syntax for combining property declarations, constructors and initialization? class Point { public function __construct( public readonly float $x = 0.0, public readonly float $y = 0.0, public readonly float $z = 0.0, ) {} } The naive transformation for this would be: class Point { public readonly float $x = 0.0; public readonly float $y = 0.0; public readonly float $z = 0.0; public function __construct(float $x = 0.0, float $y = 0.0, float $z = 0.0) { $this->x = $x; $this->y = $y; $this->z = $z; } } Which of course will not work with readonly properties as defined. Alternatively one could transform this without the default values on the properties themselves, only on the arguments. That does lose out on Reflection though. The other one is the recently declined https://wiki.php.net/rfc/object-initializer. As it basically works by first creating the object normally (including a possible constructor call), and then assigning the specified properties, this would not be compatible with readonly properties that have defaults. Other implementation approaches are possible though, but may not be easily reconcilable with the need to also call the constructor. Not really sure what I'm trying to tell you with this though :) Nikita
> > > 1. Prevent the parser from accepting default values on write-once > > > properties (parser error) > > > 2. Re-introduce them once we know what we want to do with default > values > > > (and it makes sense) > > > > Yes, this scenario definitely makes sense. I'm just not yet sold that it > > will have any negative effects > > if we don't restrict the usage of default values now. I understand that > > it's usually advantageous to be > > conservative with adding new features - especially when groping in the > dark > > - since we are the ones > > who have to support and fix them later. That's why I was hesitant to add > > property covariance to the > > proposal. > > The negative effect is if we later decide that it's too much of a problem > to have default values, because they're just too confusing (people debating > if they should be a constant, people expecting them to be overwriteable, > etc.), we can't simply remove them. That would be a large breaking change > and not allowed, so we're stuck with 'em. > > Whereas if we don't add it now, we can see if people are really clammoring > for it and in what situations. We let the PHP-using masses do the research > for us to determine how read-only-default would actually be useful, or if > it would be useful at all. Then we can add it later in a way that would > actually be useful, or decide not to do it at all. > > --Larry Garfield > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: http://www.php.net/unsub.php > >
  109056
March 16, 2020 11:52 kocsismate90@gmail.com (=?UTF-8?B?TcOhdMOpIEtvY3Npcw==?=)
> > The other one is the recently declined > https://wiki.php.net/rfc/object-initializer. As it basically works by > first > creating the object normally (including a possible constructor call), and > then assigning the specified properties, this would not be compatible with > readonly properties that have defaults. Other implementation approaches are > possible though, but may not be easily reconcilable with the need to also > call the constructor. >
I think what I'd expect from a possible object initializer feature is that it can't overwrite "write-once" properties with default values. (?) However, I'm ok to to remove support for default values because of the possible problems outlined (confusion for end users, uncertainty how it'd work together with new features etc.). It seems that the replies are more on this side, so let's be a bit more conservative than I originally wanted to be, so that we have more freedom later. Actually, I have already updated the RFC with this. Also, I made some clarifications in connection with serialization and the usage of resources. Thanks: Máté
  109057
March 16, 2020 11:57 nicolas.grekas+php@gmail.com (Nicolas Grekas)
Le lun. 16 mars 2020 à 12:52, Máté Kocsis <kocsismate90@gmail.com> a écrit :

> > > > The other one is the recently declined > > https://wiki.php.net/rfc/object-initializer. As it basically works by > > first > > creating the object normally (including a possible constructor call), and > > then assigning the specified properties, this would not be compatible > with > > readonly properties that have defaults. Other implementation approaches > are > > possible though, but may not be easily reconcilable with the need to also > > call the constructor. > > > > I think what I'd expect from a possible object initializer feature is that > it > can't overwrite "write-once" properties with default values. (?) > > However, I'm ok to to remove support for default values because of the > possible > problems outlined (confusion for end users, uncertainty how it'd work > together with new > features etc.). It seems that the replies are more on this side, so let's > be a bit more > conservative than I originally wanted to be, so that we have more freedom > later. > > Actually, I have already updated the RFC with this. Also, I made some > clarifications > in connection with serialization and the usage of resources. >
I repeat what I wrote before but all those problems would disappear if we were to bind the proposal to visibility: https://externals.io/message/108675#108753 We could even consider splitting "read" and "write" in two separate keywords, each bound to visibility, isn't it? Nicolas
  109064
March 16, 2020 15:41 larry@garfieldtech.com ("Larry Garfield")
On Mon, Mar 16, 2020, at 6:57 AM, Nicolas Grekas wrote:
> Le lun. 16 mars 2020 à 12:52, Máté Kocsis <kocsismate90@gmail.com> a écrit : > > > > > > > The other one is the recently declined > > > https://wiki.php.net/rfc/object-initializer. As it basically works by > > > first > > > creating the object normally (including a possible constructor call), and > > > then assigning the specified properties, this would not be compatible > > with > > > readonly properties that have defaults. Other implementation approaches > > are > > > possible though, but may not be easily reconcilable with the need to also > > > call the constructor. > > > > > > > I think what I'd expect from a possible object initializer feature is that > > it > > can't overwrite "write-once" properties with default values. (?) > > > > However, I'm ok to to remove support for default values because of the > > possible > > problems outlined (confusion for end users, uncertainty how it'd work > > together with new > > features etc.). It seems that the replies are more on this side, so let's > > be a bit more > > conservative than I originally wanted to be, so that we have more freedom > > later. > > > > Actually, I have already updated the RFC with this. Also, I made some > > clarifications > > in connection with serialization and the usage of resources. > > > > > I repeat what I wrote before but all those problems would disappear if we > were to bind the proposal to visibility: > https://externals.io/message/108675#108753 > > We could even consider splitting "read" and "write" in two separate > keywords, each bound to visibility, isn't it?
How would 2 separate keywords work, syntactically/visually? I can see how it would solve a larger cluster of use cases, but I'm not sure what the code would look like. :-) --Larry Garfield
  109070
March 16, 2020 16:25 nicolas.grekas+php@gmail.com (Nicolas Grekas)
> > I repeat what I wrote before but all those problems would disappear if we > > were to bind the proposal to visibility: > > https://externals.io/message/108675#108753 > > > > We could even consider splitting "read" and "write" in two separate > > keywords, each bound to visibility, isn't it? > > How would 2 separate keywords work, syntactically/visually? I can see how > it would solve a larger cluster of use cases, but I'm not sure what the > code would look like. :-) >
Hum dunno, I throw the idea to fish for interest and now I can not figure out an answer to your question... :P Back to my previous suggestion on my side, which provides the core of the benefits we strive for IMHO... Nicolas
  109081
March 16, 2020 20:39 larry@garfieldtech.com ("Larry Garfield")
On Mon, Mar 16, 2020, at 11:25 AM, Nicolas Grekas wrote:
> > > I repeat what I wrote before but all those problems would disappear if we > > > were to bind the proposal to visibility: > > > https://externals.io/message/108675#108753 > > > > > > We could even consider splitting "read" and "write" in two separate > > > keywords, each bound to visibility, isn't it? > > > > How would 2 separate keywords work, syntactically/visually? I can see how > > it would solve a larger cluster of use cases, but I'm not sure what the > > code would look like. :-) > > > > Hum dunno, I throw the idea to fish for interest and now I can not figure > out an answer to your question... :P > Back to my previous suggestion on my side, which provides the core of the > benefits we strive for IMHO... > > Nicolas
My best off-the-cuff thought would be compound name keywords. readpublic writeprotected string $foo Or something like that. Which... looks kind of ugly since I don't think we can do kebab case keywords. :-) --Larry Garfield
  109041
March 15, 2020 21:41 jakob@givoni.dk (Jakob Givoni)
On Sun, Mar 15, 2020, at 8:48 AM, Marco Pivetta wrote:
> I think what will happen is that people will start requesting for read-only > properties with default values to be over-writable-once
Exactly, I think that intuitively, developers will not see a default value as an actual "write". They will expect to be able to overwrite it once.
  109051
March 16, 2020 09:47 rowan.collins@gmail.com (Rowan Tommins)
On Sun, 15 Mar 2020 at 21:41, Jakob Givoni <jakob@givoni.dk> wrote:

> On Sun, Mar 15, 2020, at 8:48 AM, Marco Pivetta wrote: > > I think what will happen is that people will start requesting for > read-only > > properties with default values to be over-writable-once > > Exactly, I think that intuitively, developers will not see a default > value as an actual "write". > They will expect to be able to overwrite it once. >
I'm sure different people will react differently, but my intuition is quite the opposite: I would probably call the inline assignment to the property an "initial value", not a "default value", and I would intuitively compare it to assigning it in the constructor. I would also understand the intent of "write once" to be "once initialized, can't be overwritten", so would personally have no expectation that I could initialize a variable in both the property definition and the constructor. Regards, -- Rowan Tommins [IMSoP]
  109053
March 16, 2020 11:41 jakob@givoni.dk (Jakob Givoni)
On Mon, Mar 16, 2020 at 4:47 AM Rowan Tommins collins@gmail.com> wrote:
> I'm sure different people will react differently, but my intuition is quite > the opposite: I would probably call the inline assignment to the property > an "initial value", not a "default value", and I would intuitively compare > it to assigning it in the constructor. I would also understand the intent > of "write once" to be "once initialized, can't be overwritten", so would > personally have no expectation that I could initialize a variable in both > the property definition and the constructor.
Interesting, so if I understand you correctly, you think of a property with an initial/default value to be *initialized* before the constructor, but a property without it is *initialized* in the constructor or later (at first assignment of value)? Is this concept of initialized/non-initialized properties something that is more or less well-described in the language? If so, I'd like to read it. Seems like a fuzzy concept to me so far, unfortunately...
  108745
February 24, 2020 21:28 mike@newclarity.net (Mike Schinkel)
> On Feb 24, 2020, at 8:55 AM, Rowan Tommins collins@gmail.com> wrote: > > As with typed properties, I wonder if there's a way we can introduce....
Now you are talking! This gives me hope after my dejected last comment on the list. -Mike
  108714
February 21, 2020 13:41 kocsismate90@gmail.com (=?UTF-8?B?TcOhdMOpIEtvY3Npcw==?=)
> > Of course, that does leave the question of how often you need one or the > other. Maybe just the asymmetric visibility is sufficient for most > practical purposes, in which case it may not be worthwhile to introduce > readonly properties as a separate feature. >
The examples shown in my previous email are indeed not very practical, but still, I would say that the added protection against possible misuse or accidental modifications (coming from either inside or outside) would be useful. Maybe it would make more sense to forbid readonly properties with default
> values? >
As I mentioned in my response to Larry, my point of view is that default values should be allowed. If there is a big opposition against this, I'm open for a change though.
> Regarding the keyword choice, I think you can drop "sealed" from the list, > as it is an established term that affects inheritance, not mutability. Of > the choices you present, "immutable", "readonly" and "writeonce" seem like > the most viable candidates. >
Thank you for the suggestions! Sure, we can drop "sealed", and I'm ok to add "immutable" and "readonly" to the list of voting choices. I'll also extend the evaluations with your thoughts. Regard, Máté