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]