Re: Re: [RFC] Constructor Property Promotion
| From: | Nikita Popov | Date: | Wed, 06 May 2020 08:31:34 +0000 |
| Subject: | Re: Re: [RFC] Constructor Property Promotion | ||
| References: | 1 2 3 4 5 6 7 8 9 10 11 12 13 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-110034@lists.php.net to get a copy of this message | ||
On Tue, May 5, 2020 at 10:27 PM Larry Garfield <larry@garfieldtech.com>
wrote:
> On Tue, May 5, 2020, at 7:35 AM, Nikita Popov wrote:
>
>
> > Performing validation when the getAttributes() call is performed does
> sound
> > reasonable to me. We can also add a class flag to perform this validation
> > only once (if it is successful), so the cost doesn't have to be paid by
> > every single getAttributes() consumer.
> >
> > For the purpose of this RFC, I've now updated it to say that attributes
> > will be applied to both properties and parameters (
> > תծ´“76Ç…€ ã
> > https://wiki.php.net/rfc/constructor_promotion#attributes), but with an
> > explicit note that we should change the behavior prior to the PHP 8
> release
> > if it turns out to be problematic, e.g. with regards to an attribute
> > validation feature. I think this is something of a detail, and we'll be
> > mostly fine whichever way we chose, but it's hard to say right now which
> > option is best, in particular with regard to future attributes
> improvements.
> >
> > Regards,
> > Nikita
>
> Thinking on it further, there's something sitting in the back of my head
> that's bothering me. This helped me flesh it out.
>
> I like promotion, and think it will be great for simplifying code.
> However, as attributes show as we load more bells and whistles onto
> properties (even if they are desirable and useful bells and whistles) the
> level of conflict is going to get larger.
>
> Consider, today we have:
>
> class Point {
> protected int $x;
> protected int $y;
>
> public function __construct(int $x, int $y) {
> $this->x = $x;
> $this->y = $y;
> }
> }
>
> And this is needlessly verbose. With promotion, that simplifies to:
>
> class Point {
> public function __construct(protected int $x, protected int $y) {}
> }
>
> And this is good. There seems little dispute about that.
>
> But then we add attributes, and it starts to get a little odd:
>
> class Point {
> public function __construct(
> <<Positive>>
> protected int $x,
>
> <<Positive>>
> protected int $y,
> ) {}
> }
>
> And has the possible confusion between parameter attributes and property
> attributes.
>
> But... there's plenty of other things that may get added to properties.
> We've discussed the "readonly" property and the more robust assymetric
> visibility. What would that look like?
>
> class Point {
> public function __construct(
> <<Positive>>
> int $x {
> public get,
> private set,
> },
>
> <<Positive>>
> int $y {
> public get,
> private set,
> },
> ) {}
> }
>
> And that's now an awful lot of metadata to shove into, technically, a
> function signature. And if accessor methods ever happen:
>
> class Point {
> public function __construct(
> <<Positive>>
> int $x {
> public get () {
> return $this->x;
> },
> private set($new) {
> if ($new < 0) {
> throw new Exception();
> }
> $this->x = $new;
> },
> },
>
> <<Positive>>
> int $y {
> public get () {
> return $this->y;
> },
> private set($new) {
> if ($new < 0) {
> throw new Exception();
> }
> $this->y = $new;
> },
> },
> ) {}
> }
>
> And now we have methods defined inside a function signature, and that's
> just bizzarro. It also means at least 4 indent levels before you even get
> to the body of the methods.
>
> And there's probably other things that could get attached to properties at
> some point I'm not thinking of that someone will suggest.
>
> So I have to wonder, are we painting ourselves into a hole? Most of the
> uses I can think of for extra-metadata-properties are... overlapping with
> the case where you want to have constructor promotion. That means "well if
> you need to do fancy stuff just don't use promotion" becomes a
> non-sustainable answer, because the cases where you want both will increase
> rather than be an edge case.
>
> Is there a way we can build in an escape hatch for ourselves to avoid
> these issues, both attributes and for future extension?
>
> First idea off my head, which may or may not be good:
>
> Allow an alternate keyword to fill in the body of the constructor, but NOT
> fill in the property. You still define the property yourself and put all
> the extra metadata there. To wit:
>
> class Point {
> <<Positive>>
> int $x {
> public get () {
> return $this->x;
> },
> private set($new) {
> if ($new < 0) {
> throw new Exception();
> }
> $this->x = $new;
> },
> },
>
> <<Positive>>
> int $y {
> public get () {
> return $this->y;
> },
> private set($new) {
> if ($new < 0) {
> throw new Exception();
> }
> $this->y = $new;
> },
> },
>
> public function __construct(promote int $x, promote int $y) {}
> }
>
> That eliminates 2 of the 4 places you'd have to repeat the name instead of
> 3, but means the constructor isn't burdened with having to hold a
> potentially non-trivial amount of metadata. It also resolves the attribute
> question as splitting it up like this would let you do "one or the other"
> (property vs parameter).
>
> That may not be the best way, but I'm slightly uneasy with a path that
> could lead to 30 lines of property metadata squeezed into a constructor
> signature definition. :-)
>
Hey Larry,
At least for me, there's a pretty clear line on what is acceptable to use
with constructor promotion and what isn't: That line has been crossed when
your property declarations started to include their own {} blocks.
Constructor promotion is a short-hand notation for the cases where the
constructor boilerplate is much larger than the actual functionality. If
you have 100 lines of property declarations due to heavy accessor use, then
I think the value proposition of constructor promotion converges to zero,
because the constructor boilerplate is no longer a dominating factor.
Regarding your last example, something similar to this has already been
proposed (and declined) in a slightly different form in
https://wiki.php.net/rfc/automatic_property_initialization:
class Point {
public int $x { ... };
public int $y { ... };
public function __construct(int $this->x, int $this->y) {}
}
Regards,
Nikita