Re: Proposal: AS assertions

From: Date: Thu, 21 Mar 2024 15:02:22 +0000
Subject: Re: Proposal: AS assertions
References: 1 2 3 4 5 6 7  Groups: php.internals 
Request: Send a blank email to internals+get-122706@lists.php.net to get a copy of this message
On Thu, Mar 21, 2024 at 12:45 PM Rowan Tommins [IMSoP] <imsop.php@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] I don't think you are getting what I am saying. $a as int|float would be an int, float, or thrown exception. $a as int|float|null would be an int, float, or null. Robert Landers Software Engineer Utrecht NL

« previous php.internals (#122706) next »