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?