On Fri, Mar 22, 2024 at 5:51 PM Rowan Tommins [IMSoP]
<imsop....@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]

Reply via email to