On 09/05/2016 11:37 AM, Fleshgrinder wrote:

On 9/5/2016 10:26 AM, Michał Brzuchalski wrote:
I had a talk at Room11 and we discussed idea of `mutator` keyword.
There were some concerns using `mutator` as a keyword - that's because
immutable object is not being muted and also magically appeared `$clone`
would be confusing. There's an idea of creating clone before function begins
and operating simply on `$this` while it's newly created clone from
immutable
object instance and the additional keyword for such method would be for eg.
`transformer`, so basically it may look like this:

immutable class Money {
     public $amount = 0;
     public $currency;
     public function __construct($amount, $currency) {
         $this->amount = $amount;
         $this->currency = $currency;
     }
     public transformer function amount($newamount) {
         $this->amount = $newAmount; // $this actually is newly created clone
         return $this;
     }
}

$oneHundret = new Money(100, $eur);
$twoHundret = $oneHundret->amount(200);

How about that?
The thing about mutator is only partly true because setters are
generally just called mutator, it does not state anything about how it
mutates something. Note that the keyword might proof useful in
nonimmutable classes too for other use cases in the engine. Hence, I
would not throw it off board just yet.

Naming things is hard. :-) While working on PSR-13 for FIG, we ended up with the name "evolvable" for interfaces with the withFoo() methods. I am not necessarily endorsing that as the best term (we weren't super happy about it but it was better than the alternatives), but providing it as a data point.

It is true that $clone might come as a surprise and that's why I
proposed to pass it as the first argument to the mutator function.
However, you were right that that is a suboptimal proposal (and it
pretty much goes completely against my previous paragraph).

However, always providing $this as a clone is also not a good idea
because you might want to return the same instance. This really depends
on the kind of action that is desired.

It's probably much simpler to keep the clone requirement and unseal the
clone while making the __clone method protected by default.

   immutable prototype {

     protected function __clone();

   }

   immutable class Money {

     public $amount;

     public $currency;

     public function __construct(int $amount, Currency $currency) {
       $this->amount = $amount;
       $this->currency = $currency;
     }

     public function withAmount(int $amount) {
       if ($this->amount === $amount) {
         return $this;
       }

       $clone = clone $this;
       $clone->amount = $amount;

       return $clone;
     }

   }

This might seem like we haven't achieved much compared to the current
state of affairs but we actually did:

1. No introduction of additional keywords (besides immutable)
2. No surprises (magic $clone variable)
3. No hard limitation on cloning (but disallowed by default) *
4. Full freedom to developers when to clone
5. Full freedom to developers what to return

* Simply because a hard limitation is not required. Let people clone the
immutable instances if they want too. Nothing bad happens besides
wasting performance.

I think that this is the simplest and thus best solution. :)

How big of a need is it to allow returning $this instead of $clone, and/or can that be internalized somehow as well? With copy-on-write, is that really an issue beyond a micro-optimization?

As an end-user/developer, it's unclear to me how I'd know visually what scopes an object can be modified in. Basically, in the above example there's an implicit unlock-on-clone and lock-on-return. There's no clear indication of that, however, since there's no extra keywords. Is that sufficiently obvious for developers? I fear not.

Also, would the above still allow for custom clone() implementations on an immutable object or no? I' not sure off hand which I'd prefer, honestly...

Another note: This would preclude "externally immutable" objects, eg, ones that can compute and internally cache a value but are still effectively immutable as the outside world sees them. That's probably acceptable since the manual way is still available, but I thought it worth calling out.

I definitely like any of these options better than an explicit user-facing lock/unlock mechanism, as that's begging for abuse, confusion, and inconsistency.

--Larry Garfield

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

Reply via email to