Re: Proposal: AS assertions
| From: | Robert Landers | Date: | Fri, 22 Mar 2024 18:47:45 +0000 |
| Subject: | Re: Proposal: AS assertions | ||
| References: | 1 2 3 4 5 6 7 8 9 10 11 12 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-122732@lists.php.net to get a copy of this message | ||
On Fri, Mar 22, 2024 at 5:51 PM Rowan Tommins [IMSoP]
<imsop.php@rwec.co.uk> wrote:
>
> On Fri, 22 Mar 2024, at 12:58, Robert Landers wrote:
> >
> >> $optionalExpiryDateTime = $expiry as ?DateTimeInterface else new
> >> DateTimeImmutable($expiry);
> > I'm not sure I can grok what this does...
> >
> > $optionalExpiryDateTime = ($expiry === null || $expiry instanceof
> > DateTimeInterface) ? $expiry : new DateTimeImmutable($expiry)
>
> Trying to write it as a one-liner is going to make for ugly code - that's why I'd
> love to have a new way to write it! But yes, that's the right logic.
>
> With the "is" operator from the Pattern Matching draft, it would be:
>
> $optionalExpiryDateTime = ($expiry is ?DateTimeInterface) ? $expiry : new
> DateTimeImmutable($expiry);
>
> But with a clearer assertion that the variable will end up with the right type in all cases:
>
> $optionalExpiryDateTime = $expiry as ?DateTimeInterface else some_other_function($expiry);
> assert($optionalExpiryDateTime is ?DateTimeInterface); // cannot fail, already asserted by the
> "as"
>
>
> > Maybe? What would be the usefulness of this in real life code? I've
> > never written anything like it in my life.
>
> I already explained the scenario: the parameter is optional, so you want to preserve nulls; but
> if it *is* present, you want to make sure it's the correct type before proceeding. Another
> example:
> // some library function that only supports strings and nulls
> function bar(?string $name) {
> if ( $string !== null ) ...
> else ...
> }
> // a function you're writing that supports various alternative formats
> function foo(string|Stringable|int|null $name = null) {
> // we don't want to do anything special with nulls here, just pass them along
> // but we do want to convert other types to string, so that bar() doesn't reject them
> bar($name as ?string else (string)$name);
> }
This breaks my brain; in a good way I think. As you pointed out,
people could now write this:
function (int|string|null $value) {
foo($value as int|null else (int)$value);
}
Which would pass int or null down to foo. Especially because I see
something like this too often (especially with strict types):
function (int|string|null $value) {
foo((int) $value);
}
And foo() gets a 0 when $value is null and "undefined" things start happening.
This isn't really possible with any of the other syntaxes I proposed.
Now, if we are dealing with function returns:
($x as MyType else null)?->doSomething();
I don't hate it. It's a bit wordy, but still better than the alternative.
>
> To put it another way, it's no different from any other union type: at some point, you
> will probably want to handle the different types separately, but at this point in the program,
> either type is fine. In this case, the types that are fine are DateTimeInterface and null; or in the
> example above, string and null.
>
>
> > $optionalExpiryDateTime = $expiry == null ? $expiry : $expiry as
> > DateTimeInterface ?? new DateTimeImmutable($expiry as string ?? "now")
>
> If you think that's "readable" then we might as well end the conversation here.
> If that was presented to me in a code review, I'd probably just write "WTF?!"
Hahaha, yeah, I wrote that before reading my own example!
>
> I have no idea looking at that what type I can assume for $optionalExpiryDateTime after that
> line, which was surely the whole point of using "as" in the first place?
>
> Regards,
> --
> Rowan Tommins
> [IMSoP]