Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:113085 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 86223 invoked from network); 5 Feb 2021 00:08:10 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 5 Feb 2021 00:08:10 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id E15761804F2 for ; Thu, 4 Feb 2021 15:51:59 -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=-2.6 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,RCVD_IN_DNSWL_LOW,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL, SPF_HELO_PASS,SPF_NONE autolearn=no autolearn_force=no version=3.4.2 X-Spam-Virus: No X-Envelope-From: Received: from out2-smtp.messagingengine.com (out2-smtp.messagingengine.com [66.111.4.26]) (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 15:51:59 -0800 (PST) Received: from compute4.internal (compute4.nyi.internal [10.202.2.44]) by mailout.nyi.internal (Postfix) with ESMTP id 70D895C00C5 for ; Thu, 4 Feb 2021 18:51:58 -0500 (EST) Received: from imap8 ([10.202.2.58]) by compute4.internal (MEProxy); Thu, 04 Feb 2021 18:51:58 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to:x-me-proxy :x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm2; bh=uO+ki8 ePDa3bmf4ZxOFx6cn/xsqoCbbVQyokTTfq/CU=; b=F4uCbzWhj/Pt+cifAgGrU5 k30tRuCNnfQDqoa5r5qDNEdQDao9gVCK0okd13V+xVQdVxfAPDjdpJudv/ice7ZO oNx5JNe/9MjbODnPnvuSt222clfd3JP54PnsY4F5vg1nP7yhAP/pqwIGJvRMlaNB jnh++PQ3jARy30cSV0/wG0mKmOgJH4PMgTLKvHRcd+hJgKXK35TfNtCn4HU+Jnt6 KuqW9CNQZPYVVAnR+HL47BKvzZCbqL3eidu4NhzvOPCL+6Qh0lXzV+cRatHuXCrO yZjBvu920yOm2cutluNw1AM6V5KTjnyNMxAYXnHK5l/GP8uDHJBExZD0vdleg58w == X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeduledrgeehgddugecutefuodetggdotefrodftvf curfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfghnecu uegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenuc fjughrpefofgggkfgjfhffhffvufgtsehttdertderreejnecuhfhrohhmpedfnfgrrhhr hicuifgrrhhfihgvlhgufdcuoehlrghrrhihsehgrghrfhhivghlughtvggthhdrtghomh eqnecuggftrfgrthhtvghrnhepudehgfekheefgeetteekfeeiiedvfeekuddvveehtedt kefhfeffffekffegveehnecuffhomhgrihhnpehpvggrkhgurdgtohhmpdhphhhprdhnvg htnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheplhgr rhhrhiesghgrrhhfihgvlhguthgvtghhrdgtohhm X-ME-Proxy: Received: by mailuser.nyi.internal (Postfix, from userid 501) id 14F963A0074; Thu, 4 Feb 2021 18:51:58 -0500 (EST) X-Mailer: MessagingEngine.com Webmail Interface User-Agent: Cyrus-JMAP/3.5.0-alpha0-93-gef6c4048e6-fm-20210128.002-gef6c4048 Mime-Version: 1.0 Message-ID: <1767be1d-372e-4196-a9a0-6139859cc37d@www.fastmail.com> In-Reply-To: References: <1d0abb04-4987-43a9-85bc-bccc3bd6be9a@www.fastmail.com> Date: Thu, 04 Feb 2021 17:51:36 -0600 To: "php internals" Content-Type: text/plain Subject: =?UTF-8?Q?Re:_[PHP-DEV]_Analysis_of_property_visibility,_immutability,_a?= =?UTF-8?Q?nd_cloning_proposals?= From: larry@garfieldtech.com ("Larry Garfield") On Wed, Feb 3, 2021, at 8:14 AM, Nikita Popov wrote: > On Mon, Dec 28, 2020 at 9:24 PM Larry Garfield > wrote: > > > 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. > > > > The full writeup is here: > > > > https://peakd.com/hive-168588/@crell/object-properties-and-immutability > > > > I hope it proves stimulating, at least of discussion and not naps. > > > > Thanks for the analysis Larry! I want to add a couple of thoughts from my > side. > > 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... > > 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. > > Here are the pieces that I think would make up a proper solution to this > space: > > 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). > > 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. > > 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. > > 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. > > 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 = '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). > > 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. > > 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 > > public string $method { > guard($version) { > if (!in_array($version, ['1.1', '1.0', '2.0'])) throw new > InvalidArgumentException; > } > } > > then this would ensure consistent enforcement of the property invariants > regardless of how it is set. > > 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. > > 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). > > Overall, I'm still completely unsure what we should be doing :) > > Regards, > Nikita Thanks for the feedback, Nikita. And yes, on the larger scale I'm not sure what the perfect solution is either. :-) Regarding your comments first: I've thought about "record" types in the past (by-value formal structures), which would go back to by-value semantics. However, every time I think about what features we'd want them to have, I always end up back at "every possible feature of classes someone will want on records," at which point we're just double-implementing classes on a new zval type. That seems ungood. (Imagine figuring out how to do generics, and then needing to do them twice.) The alternative would be some kind of "by-value-passing" flag on class definitions, something like "byval class Foo { ... }", but I have absolutely no idea if that's even possible (at the engine level) much less desireable (at the API predictability level). To some extent you want to be able to predict in advance whether a variable will pass by value or by reference or by handle so you know what it's safe to do to it. It's also trivial to bypass by-value by passing a value by reference, thus losing all the safety that would give you. See also: Any Drupal version in the last 15 years, that *loves* passing around enormous arrays by reference so they can be modified. The question, though, is if we want immutable values or passing-safe values, which are not *quite* the same thing. You assert above that what we really want are passing-safe values. I'm... not actually sure myself which one is the true desire since they've been coupled for so long, other than modify-in-place structures don't always have good ergonomics. (I personally prefer chaining set or with methods over repeating an object name over and over again to set a value. I'm sure others will disagree.) I will note that even if we were to have a record type of some kind, initonly values still pose a challenge if they're derived from some other value that may change, or in cases where an object still can and should be cloned for reasons other than emulating immutability. Also, there are other reasons to implement vec and dict in the engine beyond just enforcing immutability, although I would want to do that even if they were done with a record type. Regarding your property accessor proposal: I've always said that initonly, asymmetric visibility, etc. are all stepping stones toward full property accessors. My understanding was that they failed before for performance reasons. If you believe those are solvable in a way that would let us skip the intermediary steps and go straight toward the full package (which would effectively let us emulate all of these other features we've been discussing), I am so totally here for it. I adore the idea of a guard method on properties. That would be useful in a huge number of places, even if nothing else makes it in. I have only 2 concerns about them: 1) I can see them being used a ton on promoted properties, so guard clauses being incompatible with promoted properties would be extremely sad. We should spend some time exploring ways to make them play nice together. 2) There are likely a huge number of cases that can be reduced to a declarative syntax, which could then be parsed, extracted, and used for creating tests, creating JS equivalents for automated form validation, and so on. A method wouldn't support that, but offers more flexibility. Which... Those two together just gave me an idea. Make it an attribute. class Foo { public function __construct( #[GuardMethod('startsWithNumber')] #[GuardRegex('[0-9]')] public string $bar ) { } public function startsWithNumber($val): bool { ... } } That would allow some validation to be baked in, in a declarative form, support arbitrary method guards, and move the code away so that it's compatible with constructor promotion. It could potentially be implemented in a way that is user-space extensible, too. I think this is worth investigating further, even independently of everything else we're discussing. I also love lazy/init/whatever properties, as that gives us the self-memoization that no other option discussed has managed. As noted above, my only concern is what happens to it if some other value it is computed off of changes, or the object is cloned, etc. One viable answer is "if it's not safe to memoize then just don't do that, dummy," which may be the answer, but as we all know PHP developers in the wild do not always think such things through. (And it's such a tempting feature that it may get over used, and get people into trouble.) Again, possibly not something we could realistically resolve but worth calling out. The descriptions around the backing property are a bit clunky. I think I follow/agree with what you're describing, but the way it's described with the underscore property is a bit misleading. I also think that making $value a magic name is not a good approach. That's not at all self-evident from context; you just have to know that is a magic value now. I would alternatively propose using the property name itself. So: class Test { public string $prop { get { return $this->prop; } set { $this->prop = $prop; } } } That way the name is predictable and logical. I don't have any good ideas on the inheritance or references front at the moment. --Larry Garfield