Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:113074 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 37147 invoked from network); 4 Feb 2021 13:41:22 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 4 Feb 2021 13:41:22 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 885A11804E3 for ; Thu, 4 Feb 2021 05:25:06 -0800 (PST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=3.4.2 X-Spam-Virus: No X-Envelope-From: Received: from mail-qv1-f45.google.com (mail-qv1-f45.google.com [209.85.219.45]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Thu, 4 Feb 2021 05:25:06 -0800 (PST) Received: by mail-qv1-f45.google.com with SMTP id n14so1661753qvg.5 for ; Thu, 04 Feb 2021 05:25:06 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=newclarity-net.20150623.gappssmtp.com; s=20150623; h=mime-version:subject:from:in-reply-to:date:cc :content-transfer-encoding:message-id:references:to; bh=66Oq1Hu67/e+Gb5sdGyAmJtxnlCtuDLrNegYgBtpqZM=; b=HWZERO2IusBQ+rN/ljhDBYDt/Uuywk+fDB9ZEltn75Vh5Zw6eb7RqQN2meB0O7L5uy kHEII56iYq3djJ56T3yFcAL/W9+s23f/rdNCNNJUV5UGlCRDJQVfbZMnySM+577Dxd/e UBtnzEa7T6zieaRNULZinvLuQYWVkckqiF1Q5vBFoMVneiUGK32oM8KZS1DuoNkD8w9J e8v8CwjeNncO2QjA6hzndTMSpj4d9luUwtsFI4Jucm6J0bP/RtinubDeEGbkfsdVZuR4 OVo2IljuNuz2y4DUh1naVrKXUgj18WI5drgRWqo6qhwSluQFe//76g2hOiH6D62lENWj Fq3w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:subject:from:in-reply-to:date:cc :content-transfer-encoding:message-id:references:to; bh=66Oq1Hu67/e+Gb5sdGyAmJtxnlCtuDLrNegYgBtpqZM=; b=RyibgE9+soWfx+aOOZ0mhI0nvA5WswWlBUu+wtZCY6HQQbuxv+H/6xp1MQiGn4ORmU BvkfnU8jLrF2OwsT3h5/plmYkhsTpYAjn5EwWEb7LlGVAWQ0VCcMFQ8yJ5lwBE5fANgT 6m6Da7Jvaey4N6AjWiCTl3XK1cRYRs5wny03OwgWTsZe2ei8b88R1lQ6O3pTdYAUr8A2 KA5GzJBzOyu5YLonL6GffpfwTkI/DIHXBRNBj4N3BajP9gWiG1BsrMY0WnnpaUmYjtdh 5b9drK44UOsO7HBZEcnxaoIXFoBJQ/HzojRYDqKt8xcMzJvv7eKl9mIBr6F0PsnCyzh9 ltfw== X-Gm-Message-State: AOAM531a1oWJWWCdA8/vGRuitaiN3oThczLp4FGQBnTlkap0c2j/hMNn MNsbeukDGwdQZejCEivXkSEUWw== X-Google-Smtp-Source: ABdhPJy8tkYx8YP3FTMKQ2cIuxkrYOBrVSO1DaFhjzIAYKLPeUGn+t+JkT85yHurKWDwpOOWCrG5YQ== X-Received: by 2002:a0c:de07:: with SMTP id t7mr7342207qvk.25.1612445105282; Thu, 04 Feb 2021 05:25:05 -0800 (PST) Received: from [192.168.1.239] (c-24-98-254-8.hsd1.ga.comcast.net. [24.98.254.8]) by smtp.gmail.com with ESMTPSA id e7sm4541347qtj.48.2021.02.04.05.25.04 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 04 Feb 2021 05:25:04 -0800 (PST) Content-Type: text/plain; charset=us-ascii Mime-Version: 1.0 (Mac OS X Mail 13.4 \(3608.120.23.2.4\)) In-Reply-To: Date: Thu, 4 Feb 2021 08:25:03 -0500 Cc: php internals Content-Transfer-Encoding: quoted-printable Message-ID: <0AEC52CE-EFC0-4C5A-A4B8-6D91419B32AD@newclarity.net> References: <1d0abb04-4987-43a9-85bc-bccc3bd6be9a@www.fastmail.com> To: Nikita Popov X-Mailer: Apple Mail (2.3608.120.23.2.4) Subject: Re: [PHP-DEV] Analysis of property visibility, immutability, and cloning proposals From: mike@newclarity.net (Mike Schinkel) > On Feb 3, 2021, at 9:14 AM, Nikita Popov wrote: >=20 > On Mon, Dec 28, 2020 at 9:24 PM Larry Garfield = > wrote: >=20 >> There's been a number of discussions of late around property = visibility >> and how to make objects more immutable. Since it seems to have been >> well-received in the past, I decided to do a complete analysis and = context >> of the various things that have been floated about recently. >>=20 >> The full writeup is here: >>=20 >> = https://peakd.com/hive-168588/@crell/object-properties-and-immutability >>=20 >> I hope it proves stimulating, at least of discussion and not naps. >>=20 >=20 > Thanks for the analysis Larry! I want to add a couple of thoughts from = my > side. >=20 > First of all, I think it's pretty clear that "asymmetric visibility" = is the > approach that gives us most of what we want for the least amount of = effort. > Asymmetric visibility has clear semantics, is (presumably) trivial to > implement, and gives immutability guarantees that are "good enough" = for > most practical purposes. It's the pragmatic choice, and PHP is all = about > pragmatism... >=20 > That said, I don't think that asymmetric visibility is the correct = solution > to this problem space -- I don't think asymmetric visibility is ever = (or > only very rarely) what we actually want, it's just a good enough > approximation. Unfortunately, the alternatives are more complex, and = we > have a limited budget on complexity. >=20 > Here are the pieces that I think would make up a proper solution to = this > space: >=20 > 1. initonly properties. This is in the sense of the previous "write = once > properties" proposal, though initonly is certainly the better name for = the > concept. Initonly properties represent complete immutability both = inside > and outside the class, and I do believe that this is the most common = form > of immutability needed (if it is needed at all). >=20 > Of course, as you correctly point out, initonly properties are = incompatible > with wither patterns that rely on clone-then-modify implementations. I > think that ultimately, the "wither pattern" is an artifact of the fact = that > PHP only supports objects with by-handle semantics. The "wither = pattern" > emulates objects with by-value semantics, in a way that is verbose and > inefficient. >=20 > I do want to point out that your presentation of copy-on-write when it > comes to withers is not entirely correct: When you clone an object, = this > will always result in a full copy of the object, including all its > properties. If you call a sequence of 5 wither methods, then this will > create five objects and perform a copy of all properties every time. = There > is really no copy-on-write involved here, apart from the fact that = property > values (though not the property storage) can still be shared. >=20 > 2. This brings us to: Objects with by-value semantics. This was = discussed > in the thread, but I felt like it was dismissed a bit prematurely. >=20 > Ultimately, by-value semantics for objects is what withers are = emulating. > PSR-7 isn't "immutable", it's "mutable by-value". "Immutable + = withers" is > just a clumsy way to emulate that. If by-value objects were supported, = then > there would be no need for wither methods, and the "clone-then-modify" > incompatibility of initonce properties would not be a problem in = practice. > You just write $request->method =3D 'POST' and this will either = efficiently > modify the request in-place (if you own it) or clone it and then = modify it > (if it is shared). >=20 > Another area where by-value objects are useful are data structures. = PHP's > by-value array type is probably one of those few instances where PHP = got > something right in a major way, that many other languages got wrong. = But > arrays have their own issues, in particular in how they try to service = both > lists and dictionaries at the same time, and fail where those = intersect > (dictionaries with integer keys or numeric string keys). People = regularly > suggest that we should be adding dedicated vector and dictionary = objects, > and one of the issues with that is that the resulting objects would = follow > the usual by-handle semantics, and would not serve as a mostly drop-in > replacement for arrays. It is notable that while HHVM/Hack initially = had > vec and dict object types, they later created dedicated by-value types = for > these instead. >=20 > 3. Property accessors, or specifically for your PSR-7 examples, = guards. The > __clone related issues you're mostly dealing with in your examples are > there because you need to replicate the validation logic in multiple > places. If instead you could write something like >=20 > public string $method { > guard($version) { > if (!in_array($version, ['1.1', '1.0', '2.0'])) throw new > InvalidArgumentException; > } > } >=20 > then this would ensure consistent enforcement of the property = invariants > regardless of how it is set. >=20 > Circling back, while I think that a combination of these features = would be > the "proper" solution to the problem, they also add quite a bit of > complexity. Despite what I say above, I'm very much not convinced that > adding support for by-value objects is a good idea, due to the = confusion > that two different object semantics could cause, especially if writing > operations on them are not syntactically distinct. >=20 > I've written up an initial draft for property accessors at > https://wiki.php.net/rfc/property_accessors, but once again I get the > distinct impression that this is adding a lot of language complexity, = that > is possibly not justified (and it will be more complex once = inheritance is > fully considered). I wish PHP had property accessors like this a decade ago. Question/Comments: 1. Interfaces: Would it be possible to define in an interface that a = property is readable but cannot be writable? Your example seems to me = to imply not. 2. Traits: As they are not mentioned, does that mean property accessors = would implicitly be supported or explicitly not supporters? If the = latter, why not? 3. isset(): Why does isset() throw an Error on write-only property? Why = shouldn't it just return true, or return !isnull($this->{backing_prop})? 4. get_object_vars()/get_class_vars(): These are used often when looping = through properties to access or assign as they are much more convenient = (and probably faster?) than using Reflection but with get-only and = set-only they would become rather useless unless a developer could = specify to return only get-only or only set-only properties. Can we = also add an optional parameter to both functions to allow passing a = flag, something like VARS_GET_ONLY and VARS_SET_ONLY? =20 5. Write-only properties: StackOverflow has a question[1] with several = answers that give some good reasons for write-only properties, not the = least of which is DI via properties vs. constructors. 6. Basic accessors: In the 4th example class Test in the "Basic = accessors" section the get() uses a $value variable. Should this be = $this->value, or is $value come from somewhere else? 7. var: +1 for requiring visibility specified on all assessors if any = visibility is specified. 8. Backing properties: Can you consider making backing properties to = always have the same name as the property but with a leading underscore? = This would: a. Allow them to be easily recognized when scanning code,=20 b. (Maybe?) allow on generating them when you see an underscore property = in code,=20 c. Allow internally accessing the raw backing property internally vs the = accessed code, and=20 d. Having both $prop and $_prop could help while debugging 9. lazy(): +1 for init() independent of get(), instead of lazy() 10. set() vs guard: Why does set() have an explicitly declared $value = parameter but guard does not? Shouldn't that be consistent? I'd drop it = from set() to reduce verbosity, but adding to guard() would work too. 11. get() and guard: Explicitly there should be no interaction (right?) 12. set() and guard: Explicitly first guard is called and if successful = set() is called, otherwise set() is not called (right?) 13. Illegal constructor promotion: Seems an unfortunate limitation. Can = you provide an example in the RFC of "embedding of very large property = declarations in the constructor signature" that would be considered = problematic? 14. Parent accessors: What am I missing here? Why would "parent:$prop" = be unavailable/problematic? 15. Incompatible inheritance: It seems "by-value" array properties are = the only problem? Can overriding explicitly declared arrays with = accessor properties be disallowed but everything else allowed? I know it = would be inconsistent and unfortunate, but I question whether an array = as a property should not be better encapsulated anyway and thus be a = reason an educated developer would avoid array properties in parent = classes except for use-cases where accessor property would likely never = be needed? Hope this list helps. -Mike [1] = https://stackoverflow.com/questions/2213879/do-write-only-properties-have-= practical-applications