On Thu, Mar 21, 2024 at 12:45 PM Rowan Tommins [IMSoP]
<imsop....@rwec.co.uk> wrote:
>
> On 20/03/2024 23:05, Robert Landers wrote:
>
> In other
> words, I can't think of a case where you'd actually want a Type|null
> and you wouldn't have to check for null anyway.
>
>
> It's not about having to check for null; it's about being able to distinguish 
> between "a null value, which was one of the expected types" and "a value of 
> an unexpected type".
>
> That's a distinction which is made everywhere else in the language: parameter 
> types, return types, property types, will all throw an error if you pass a 
> Foo when a ?Bar was expected, they won't silently coerce it to null.
>
>
>
> If you think about it, in this proposal, you could use it in a match:
>
> // $a is TypeA|TypeB|null
>
> match (true) {
>   $a as ?TypeA => 'a',
>   $a as ?TypeB => 'b',
>   $a === null => 'null',
> }
>
>
> That won't work, because match performs a strict comparison, and the as 
> expression won't return a boolean true. You would have to do this:
>
> match (true) {
>   (bool)($a as ?TypeA) => 'a',
>   (bool)($a as ?TypeB) => 'b',
>   $a === null => 'null',
> }
>
> Or this:
>
> match (true) {
>   ($a as ?TypeA) !== null => 'a',
>   ($a as ?TypeB) !== null => 'b',
>   $a === null => 'null',
> }
>
>
> Neither of which is particularly readable. What you're really looking for in 
> that case is an "is" operator:
>
> match (true) {
>   $a is TypeA => 'a',
>   $a is TypeB => 'b',
>   $a === null => 'null',
> }
>
> Which in the draft pattern matching RFC Ilija linked to can be abbreviated to:
>
> match ($a) is {
>   TypeA => 'a',
>   TypeB => 'b',
>   null => 'null',
> }
>
>
> Of course, in simple cases, you can use "instanceof" in place of "is" already:
>
> match (true) {
>   $a instanceof TypeA => 'a',
>   $a instanceof TypeB => 'b',
>   $a === null => 'null',
> }
>
>
>
> Including `null` in that type
> seems to be that you would get null if no other type matches, since
> any variable can be `null`.
>
>
> I can't think of any sense in which "any variable can be null" that is not 
> true of any other type you might put in the union. We could interpret 
> Foo|false as meaning "use false as the fallback"; or Foo|int as "use zero as 
> the fallback"; but I don't think that would be sensible.
>
> In other words, the "or null on failure" part is an option to the "as" 
> expression, it's not part of the type you're checking against. If we only 
> wanted to support "null on failure", we could have a different keyword, like 
> "?as":
>
> $bar = new Bar;
> $bar as ?Foo; // Error
> $bar ?as Foo; // null (as fallback)
>
> $null = null;
> $null as ?Foo; // null (because it's an accepted value)
> $null ?as Foo; // null (as fallback)
>
> A similar suggestion was made in a previous discussion around nullable casts 
> - to distinguish between (?int)$foo as "cast to nullable int" and (int?)$foo 
> as "cast to int, with null on error".
>
>
> Note however that combining ?as with ?? is not enough to support "chosen 
> value on failure":
>
> $bar = new Bar;
> $bar ?as ?Foo ?? Foo::createDefault(); // creates default object
>
> $null = null;
> $null ?as ?Foo ?? Foo::createDefault(); // also creates default object, even 
> though null is an expected value
>
> That's why my earlier suggestion was to specify the fallback explicitly:
>
> $bar = new Bar;
> $bar as ?Foo else null; // null
> $bar as ?Foo else Foo::createDefault(); // default object
>
> $null = null;
> $nulll as ?Foo else null; // null
> $null as ?Foo else Foo::createDefault(); // also null, because it's an 
> accepted value, so the fallback is not evaluated
>
> Probably, it should then be an error if the fallback value doesn't meet the 
> constraint:
>
> $bar = new Bar;
> $bar as Foo else null; // error: fallback value null is not of type Foo
> $bar as ?Foo else 42; // error: fallback value 42 is not of type ?Foo
>
>
>
> Regards,
> --
> Rowan Tommins
> [IMSoP]

Another way of thinking about is:

$x = $a as null

What do you expect $x to be?

Reply via email to