Re: RFC - Immutable classes

From: Date: Wed, 07 Sep 2016 13:09:16 +0000
Subject: Re: RFC - Immutable classes
References: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  Groups: php.internals 
Request: Send a blank email to internals+get-95748@lists.php.net to get a copy of this message
@Mathieu How about that: https://gist.github.com/brzuchal/e7b721e22a19cca42ec0d1f597a23baf We've discussed this could be best option, when invoking transformer method (or whatever we call it) there is $this = clone $this invoked under the hood. 2016-09-07 14:53 GMT+02:00 Mathieu Rochette <mathieu@rochette.cc>: > a few remarks on mutator methods: > > - It could be a nice way to solve the "create another one almost the > same" use case. > - I don't mind if $clone is an explicit parameter or magically > available > - what happens if I call other function/methods with this $clone > before the end of the function ? > - and the only downside: I have to make a method just for cloning. > that means I have to call a mutator multiple times if I want to make a > bunch of clone, eg: > > because of the last point, I think I'd like the seal the clone at then end > of the block/method better, here are 2 examples to illustrate what I mean > class immutable foo { > private $prop = 0; > > public function __construct($v) {$this->prop = $v;} > > public function bar(obj $o) { > $e->makeSomethingWith($this->cloneAndEdit(42)); > } > > public function many($n) { > $a = []; > for ($i = 0; $i < $n; $i++) { > $a[] = $this->cloneAndEdit($i)); > } > return $a; > > } > > public function mut cloneAndEdit($n) { > $clone->prop = $n; > } > } > > // vs > > class immutable foo { > private $prop = 0; > > public function __construct($v) {$this->prop = $v;} > > public function bar(obj $o) { > $c = $clone $this; > $c->prop = 42; > $e->makeSomethingWith($c); > } > > public function many($n) { > $a = []; > for ($i = 0; $i < $n; $i++) { > $a[] = $c = $clone $this; > $c->prop = 42; > } > return $a; > } > } > > I understand that it's still not clear exactly when the object should be > sealed but if it can works this one would have my preference > On 04/09/2016 14:10, Michał Brzuchalski wrote: > > 2016-09-04 10:55 GMT+02:00 Fleshgrinder <php@fleshgrinder.com> > <php@fleshgrinder.com>: > > > Hi Chris! > > On 9/3/2016 5:00 PM, Chris Riley wrote: > > - Properties can be declared immutable. Immutable properties may only be > changed under two circumstances: a) In the objects constructor b) If they > are null (This enables setter injection if required) > > > The constraint b) would make the object mutable and defeat the purpose > of the immutable modifier since any property could change at any time if > it was NULL at the beginning. Requiring syncing in concurrent environments. > > On 9/3/2016 5:00 PM, Chris Riley wrote: > > - Arrays assigned to immutable properties would not be possible to change > > > Array support would definitely be very nice. I mean, we have constant > arrays already, hence, it is possible. > > On 9/3/2016 5:00 PM, Chris Riley wrote: > > - Objects assigned to immutable properties would be possible to change, > > so > > long as the same object remained assigned to the property. > > > This would once more lead to mutability and the constraint of > immutability would be violated. > > On 9/3/2016 5:00 PM, Chris Riley wrote: > > From a developer adoption point of view, I think these two points are > important to making immutable classes generally useful. Without 1, it > > will > > be a nuisance to use 3rd party libraries esp those which retain > compatibility for PHP < 7.2. Without 2 you block the ability to use > > setter > > injection, which I personally would be in favour of if it meant that devs > stopped using it - it wouldn't - they would simply not use immutable > classes, loosing the benefits thereof. > > > The adoption of the feature will be halted until 7.2 is widely available > in bigger projects. That is most certainly right. However, we should aim > for the best, most useful, and future proof solution and not towards the > one that's adopted very fast but lacks some important constraints. > Having truly immutable objects is required in concurrent scenarios and > such scenarios are in the future for PHP and not in the past. > > Regarding setter injection: I do not see the need for it at all in the > context of immutable objects. In the end we are talking about value > objects here and they should not have any optional dependencies. Maybe > you could come up with a use case to illustrate the need? > > On 9/3/2016 5:00 PM, Chris Riley wrote: > > Dealing with the clone issue some of my ideas since then were: > > - Seal/Unseal (As per Larry's suggestion) > - Parameters to __clone; in this instance the clone method would be > > allowed > > to change properties of the object as well as the constructor. This feels > like it may breach the principal of least surprise as cloning an object > > no > > longer guarantees an exact copy. > - A new magic method __mutate($property, $newvalue) called instead of a > fatal error when a property is changed. This probably lays too many traps > for developers for it to be a good idea. > - Implicitly returning a new object whenever a property is changed. > > Similar > > reservations to the above. > - A new magic method __with($newInstance, $args) and a keyword with that > > is > > used in place of clone eg $x = $y with ($arg1, $arg2); in this instance, > __with receives a clone of $y (after calling __clone) and an array > > [$arg1, > > $arg2] the with magic method is allowed to mutate $newInstance and must > return it. This is currently my favoured solution > > > How does one know which property is to be mutated in the __with method? > You should also not underestimate the performance hit and the branching > since you only want to change the properties that changed based on the > data from the passed array. > > I have a third proposal after giving this some more thought. Inspired by > Rust's approach to mark mutation explicitly. > > final immutable class ValueObject { > > public $value; > > public mutator [function] withValue($clone, $value): static { > $clone->value = $value; > } > > } > > > > Providing mutator | mut keyword > as method modifier sounds liek a good > idea, > althought passing $clone parameter as first additional param > could break > method declaration and would be misleading. > > Assuming mutator method is designed to return mutated clone of immutable > object > having $clone variable could be handled internally without > breaking > method declaration. > > Such variable could be unlocked while in mutator method and locked on > return. > I was thinking about additional check if such mutator returns > $clone but > not $this > but I don't see the need of it - assuming there is no what to change in some > circumstances ther would be also possible to return $this. > > The return type declaration self could increase readability, > but should > not be required, > as some developers doesn't already use return types. > > > > A mutator function always receives the mutable clone as first argument > and always returns that one. Users can have a return but they must > return the clone (hence the static return type declaration). > > $vo1 = new ValueObject(1); > $vo2 = $vo1->withValue(2); > > Calls are of course without the clone as it is handled by the engine. > There is no special branching necessary and no performance hit at all > while the logic is kept in the place where it is required. > > -- > Richard "Fleshgrinder" Fussenegger > > > > > -- regards / pozdrawiam, -- Michał Brzuchalski brzuchalski.com

« previous php.internals (#95748) next »