Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:95699 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 76772 invoked from network); 6 Sep 2016 16:01:43 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 6 Sep 2016 16:01:43 -0000 Authentication-Results: pb1.pair.com smtp.mail=larry@garfieldtech.com; spf=permerror; sender-id=unknown Authentication-Results: pb1.pair.com header.from=larry@garfieldtech.com; sender-id=unknown Received-SPF: error (pb1.pair.com: domain garfieldtech.com from 66.111.4.28 cause and error) X-PHP-List-Original-Sender: larry@garfieldtech.com X-Host-Fingerprint: 66.111.4.28 out4-smtp.messagingengine.com Received: from [66.111.4.28] ([66.111.4.28:38982] helo=out4-smtp.messagingengine.com) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id 48/D2-14350-668EEC75 for ; Tue, 06 Sep 2016 12:01:42 -0400 Received: from compute3.internal (compute3.nyi.internal [10.202.2.43]) by mailout.nyi.internal (Postfix) with ESMTP id 36F5420656 for ; Tue, 6 Sep 2016 12:01:40 -0400 (EDT) Received: from frontend2 ([10.202.2.161]) by compute3.internal (MEProxy); Tue, 06 Sep 2016 12:01:40 -0400 DKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d= messagingengine.com; h=content-transfer-encoding:content-type :date:from:in-reply-to:message-id:mime-version:references :subject:to:x-sasl-enc:x-sasl-enc; s=smtpout; bh=hT+w3eRFQRkO5gA q2Rp2ELQzuyc=; b=HKP10u9uCXlMoapJUVxL0ZY+PyRsdf6bwWk6PS/irI0JM2i Ki2Dbp61GZe2dABDr3wTxqTqHfyEx9LgAlugtkmvgMmAbTo7RGYcwtmbPEG2vsQk 1olw6nj5DIYEk5/R4DgNu21zLeWCed4qgK9PDPcKMZ1tgWbc0u3C7L6nd9p8= X-Sasl-enc: RSJEssvAqTWQUW1BJv4BHFKQwVdDvNgQ9M4rLLpADDD5 1473177699 Received: from [192.168.42.5] (c-50-178-40-84.hsd1.il.comcast.net [50.178.40.84]) by mail.messagingengine.com (Postfix) with ESMTPA id DE2D5CCE7E for ; Tue, 6 Sep 2016 12:01:39 -0400 (EDT) To: internals@lists.php.net References: <0e71d28e-1d64-5372-b58d-e54c7afae3b8@fleshgrinder.com> <642a6e78-90ea-cbf0-ec1c-376c24e568c5@fleshgrinder.com> <0800a5ca-3d14-c541-1a1a-2574ec802b8c@fleshgrinder.com> Message-ID: <83fa661e-2d3d-6548-a506-fb969be31c0e@garfieldtech.com> Date: Tue, 6 Sep 2016 11:01:39 -0500 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.2.0 MIME-Version: 1.0 In-Reply-To: <0800a5ca-3d14-c541-1a1a-2574ec802b8c@fleshgrinder.com> Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 8bit Subject: Re: [PHP-DEV] RFC - Immutable classes From: larry@garfieldtech.com (Larry Garfield) 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