Re: Proposal: AS assertions
| From: | Robert Landers | 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