Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:118546 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 48663 invoked from network); 31 Aug 2022 20:43:46 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 31 Aug 2022 20:43:46 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 14CF71804AB for ; Wed, 31 Aug 2022 13:43:45 -0700 (PDT) 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.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_LOW,SPF_HELO_PASS, SPF_NONE,T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=3.4.2 X-Spam-ASN: AS29838 64.147.123.0/24 X-Spam-Virus: No X-Envelope-From: Received: from wout3-smtp.messagingengine.com (wout3-smtp.messagingengine.com [64.147.123.19]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Wed, 31 Aug 2022 13:43:44 -0700 (PDT) Received: from compute1.internal (compute1.nyi.internal [10.202.2.41]) by mailout.west.internal (Postfix) with ESMTP id E94E83200786 for ; Wed, 31 Aug 2022 16:43:40 -0400 (EDT) Received: from imap50 ([10.202.2.100]) by compute1.internal (MEProxy); Wed, 31 Aug 2022 16:43:41 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= garfieldtech.com; h=cc:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:sender:subject:subject:to:to; s=fm1; t=1661978620; x= 1662065020; bh=W9NTQwEmQ62Zrxyj/BjHGIShxdAo63GHojaHM8Se+cU=; b=W kUjQkexlfBqbzcRku2KGCPFILWLVPhIYLtKMFbnWULvcvDq4GviH9ZQc5DodwW72 e3X/5ayqzacTtqHETfLDLg9OX4oimowcGAc+5q2XAvGjysEsI7Uwp3SEcM9No1wa 3llHxpB8hiUqibyIklUt+iL2nhmK5QtKQm6wEDKJenoZGEjNT4mR9LqMrxGc+xc3 F6BWXuNyK/zQxxWjE694IdskzAKMFwxFNPTYq3R8nG0S8sBNrpJuPLntASqHGFGx Npy5Bp5cI9gRmht/th4AP2uZPeWZIz2xZAlwPhlJF6G1bXoC3VtTSmrHNexHAxaP lfWfdxq8M7xCiZZqfSORg== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:date:date:feedback-id :feedback-id:from:from:in-reply-to:in-reply-to:message-id :mime-version:references:reply-to:sender:subject:subject:to:to :x-me-proxy:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s= fm1; t=1661978620; x=1662065020; bh=W9NTQwEmQ62Zrxyj/BjHGIShxdAo 63GHojaHM8Se+cU=; b=hrcjhqC/rG/Cn7hykHJVlLsPGdyoOFztjnOurp8ARo+F U4nrlRE1MQypvYJ2F4YsswFl8hmphSgzP74VPrzWy++QDr893Hnnxy7zpG7Wtg9U rz+IRU3evFzCfatNNAblx9q3hgUJLolRRq2H2nbfLtgnvLTAGUhL9mclW4KTcLnA Y8Lr1etzjhvaMwXW2OIRSaNzD2Ya9D/I+P5gb1uobjDN5o9enwtD4YxGEp082/rL fGYo6UCLNSNvIfLxauvlxiWE0DBhRr7eGniDHv7AzDKJ0nVQ32up7/NeLYEWpc/u GqsKQUUpiTg4Rapz+4LoXzk6a/neH0TxIIBRsm2hMQ== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvfedrvdekiedguddvhecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfgh necuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmd enogfuuhhsphgvtghtffhomhgrihhnucdlgeelmdenucfjughrpefofgggkfgjfhffhffv ufgtsehttdertderredtnecuhfhrohhmpedfnfgrrhhrhicuifgrrhhfihgvlhgufdcuoe hlrghrrhihsehgrghrfhhivghlughtvggthhdrtghomheqnecuggftrfgrthhtvghrnhep heejgfehhfelveetteelledtteehuddvueduffefgfehhfetjedvieffteegteeunecuff homhgrihhnpehphhhprdhnvghtpdhgohhoghhlvgdrtghomhenucevlhhushhtvghrufhi iigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehlrghrrhihsehgrghrfhhivghlug htvggthhdrtghomh X-ME-Proxy: Feedback-ID: i8414410d:Fastmail Received: by mailuser.nyi.internal (Postfix, from userid 501) id 3105D1700082; Wed, 31 Aug 2022 16:43:40 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface User-Agent: Cyrus-JMAP/3.7.0-alpha0-841-g7899e99a45-fm-20220811.002-g7899e99a Mime-Version: 1.0 Message-ID: <81b41335-afa1-40b7-9eb6-4664304812ec@www.fastmail.com> In-Reply-To: References: Date: Wed, 31 Aug 2022 15:43:19 -0500 To: "php internals" Content-Type: text/plain Subject: Re: [PHP-DEV] [RFC] Asymmetric visibility From: larry@garfieldtech.com ("Larry Garfield") On Fri, Aug 5, 2022, at 12:08 PM, Larry Garfield wrote: > Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3: > Asymmetric Visibility. > > https://wiki.php.net/rfc/asymmetric-visibility > > Details are in the RFC, but it's largely a copy of Swift's support for the same. Hi folks. Ilija and I have been discussing and experimenting extensively based on the feedback to the first draft. Here's our thoughts so far, some additional data, and a request for feedback. (These changes haven't been made to the RFC just yet, as there's outstanding questions for which we want feedback below.) 1. Rather than `readonly` being forbidden on asymmetric properties, it's better to redefine it as "write once, and if no `set` visibility is defined, make it private." That would still allow for `public protected(set) readonly string $blah` (to work around existing limitations in `readonly`), but also more or less preserve the current syntax in a logical way. 2. Given that... we're not sure that it makes sense to have any visibility scope other than `get`/`set`. `once` becomes unnecessary with that understanding of `readonly`, and we cannot think of any other examples that are not a stretch. So that axis of extensibility is of minimal importance. 3. Since there has been some confusion, our goal *is* to implement property accessors in the future as a follow-on to this RFC. However, conceptually property "hooks" and asymmetric visibility are separate features. They also both have ample bikeshed potential. That's why we're proposing them as two separate RFCs. (Technically property hooks could also be done first, but this one is smaller.) That said, it's become obvious that we cannot avoid discussing property hooks at the same time since the syntax does overlap. So in the discussion below I will include property hooks, but deliberately gloss over parts that are not relevant right now. Please humor me and do the same. :-) 4. In concept, there's two main syntactic models for property hooks/accessors: Javascript/Python style (annotated methods) and Swift/C# style (method-esque code blocks on properties). We were trying to keep our options open and avoid a syntax that locks us into one or the other. However, we've concluded that the annotated methods style just wouldn't work at all in a language with explicit types and visibility controls. We've therefore made the executive decision that only the Swift/C# style is an option. 5. That leaves the basic question of "where to put the visibility," as Swift and C# put them in different places. (There are other differences not relevant for now.) Swift: ```swift class User { public private(set) var first: String public private(set) var last: String public private(set) fullName: String { get { ... } set { ... } } } ``` C#: ```csharp class User { public string first { get; private set; } public string last { get; private set; } public string fullName { get { ... } private set { ... } } } ``` The relevant difference is that in Swift, all visibility is on the left of the property while in C# it is split between the left and right. The main argument for Swift-style: Keep everything in one place, and if there's no property hooks we don't even have to think about it. The main argument for C#-style: It avoids repeating a keyword (eg, `set`) if using both features. It *may* be possible to omit the `get` in the C# example above if it's just the default. Ilija is still working to figure that out. ## References make everything worse One other caveat to consider is (naturally) references. A `get` hook cannot return by reference, as that would allow setting the value via its reference and bypassing a `set` hook. A side effect of that restriction is that arrays cannot be modified in place on properties with a `get` hook. That is, assuming the `$bar` property is an array that leverages the extra hooks above (by whatever syntax), the following is not possible: ```php $f = new Foo(); $f->bar[] = 'beep'; ``` That would require returning `$bar` by reference so that the `[]` operation could apply to it directly. However, that then bypasses any restrictions imposed by a `set` hook (such as the values had to be integers). The following, however, would be legal: ```php $f = new Foo(); $b = $f->bar; $b[] = 'beep'; $f->bar = $b; ``` That problem wouldn't happen if all we're changing is visibility, however. That means in the C# style, we would need to differentiate whether the block on the right is intended to disable references or not. Ilija has proposed `raw` to indicate that. That would mean: ```php class Test { public $a { private set; } public $b { private raw; } public function inScope() { echo $this->a; // echo $this->getA(); $this->a = 'a'; // $this->setA('a'); echo $this->b; // echo $this->b; $this->b = 'b'; // $this->b = 'b'; } } $test = new Test(); echo $test->a; // echo $test->getA(); $test->a = 'a'; // Error, set operation not accessible echo $test->b; // echo $test->getB(); $test->b = 'b'; // Error, set operation not accessible ``` The take-away for the asymmetric visibility only case is that we would need to use `raw` instead of `set`, in order to avoid confusion later with accessor hooks and whether or not to disable references. The Swift-style syntax, as it does not require any hook-like syntax to just control visibility, does not have this problem. There is no ambiguity to resolve, so no need for a `raw` keyword. ## Common use cases While there are a myriad of possible ways to use both asymmetric visibility and property accessor hooks, we anticipate three usage patterns to predominate: 1. Public read, private/protected write, but that's it. 2. Public read, public write, but some extra validation on set. 3. Public lazily-derived value, no write. In particular, it seems to us (though we have no precise data to back it up) that it is unlikely that both asymmetric visibility and property hooks would be used at the same time, in the typical cases. That should inform the discussion about syntax. ## Comparison For the moment I'm just going to use Swift-style as-is. There's a discussion later to be had about minor variations on it, as suggested by a few people in the previous thread. Stand by. Length comparison: ```php // Current code, for comparison public readonly string $first; // Swift-ish public private(set) string $first; // C#-ish, note "raw" because there's no hook public string $first { get; private raw; } ``` Both options suggest possible shorthands: ```php // Current code, for comparison public readonly string $first; // Swift-ish private(set) string $first; // C#-ish, assuming it can be done public string $first { private raw; } ``` If combining with `readonly`, we get: ```php // Current code, for comparison public readonly string $first; // Swift-ish public protected(set) readonly string $first; protected(set) readonly string $first; // C#-ish public readonly string $first { get; private set; } public readonly string $first { private raw; } ``` If we mix in for-reals accessor hooks, we get: ```php // Swift-ish public private(set) string $first { get { ... } set { ... } } // C#-ish, note now we're using "set". public string $first { get { ... } private set { ... } } ``` If other hooks are added (Swift has `beforeSet` and `didSet`, and we intend to include those as well for PHP), they don't really make sense to have visibility modifiers on them. It's arguable if `isset` or `unset` (to mirror the existing magic methods) would want those. Does it make sense to have "public read, public set, but private unset"? Or is `unset` just controlled by `set`? Unclear. Another complication is that, for forward compatibility, we would need a way to disable references on no-hook properties so that hooks could be added later without a change in reference behavior. Given the postfix syntax for hooks, we believe the most natural way to do that would be: ```php // Swift-ish public string $foo {} // C#-ish public string $foo {} // which is shorthand for: public string $foo { public raw; } ``` Again, the C# model implies a bit more complexity and nuance. ## Questions Question 1: Which general model (Swift or C#) is preferable, and why? (At the moment, Ilija and I disagree on which one is preferable. Hence, poll.) ## If Swift If we go with Swift-style, several people suggested alternative syntax options. This is *mostly* a bikeshed question, so I will just list them: ```php public private(set) string $first; public private:set string $first; public:private string $first; public private string $first; public(set: private) string $first; ``` Some of which have natural shorthands: ```php private(set) string $first; private:set string $first; :private string $first; N/A N/A ``` Question 2: IF we go with Swift-style, which of the above syntaxes would you prefer, and why? Please answer both questions only via this poll so can collect actual data. https://docs.google.com/forms/d/e/1FAIpQLSefq15VvGNIXSnQaMTl3RW451w0E8oesny8c4PLqmKl8HhQ-Q/viewform Thank you all for your time and input. --Larry Garfield and Ilija Tovilo