Type casting syntax

php.internals

Max Semenik

5 years ago
I've been thinking about extending PHP's cast syntax to user-defined types, e.g. not only (int)$foo but also (MyClass)$foo. Currently, a T_STRING in parentheses is always treated as a constant - would it be acceptable to hijack this syntax when used in unary operation context, i.e. "(" T_STRING ")" expr? If not, any other alternatives?
-- Best regards, Max Semenik

Benjamin Morel

5 years ago
> I've been thinking about extending PHP's cast syntax to user-defined types, > e.g. not only (int)$foo but also (MyClass)$foo. Currently, a T_STRING in > parentheses is always treated as a constant - would it be acceptable to > hijack this syntax when used in unary operation context, i.e. "(" T_STRING > ")" expr? If not, any other alternatives?
Hi, I proposed something similar a while back, you might want to have a look at this discussion: https://externals.io/message/105332#105367 - Benjamin

Olle Härstedt

5 years ago
On Sat, 10 Jul 2021, 13:05 Benjamin Morel, <benjamin.morel@gmail.com> wrote:
> > I've been thinking about extending PHP's cast syntax to user-defined > types, > > e.g. not only (int)$foo but also (MyClass)$foo. Currently, a T_STRING in > > parentheses is always treated as a constant - would it be acceptable to > > hijack this syntax when used in unary operation context, i.e. "(" > T_STRING > > ")" expr? If not, any other alternatives? > > > Hi, I proposed something similar a while back, you might want to have a > look at this discussion: > > https://externals.io/message/105332#105367 > > - Benjamin >
Maybe start filling the holes for built-in types? Callable... Olle

Marco Pivetta

5 years ago
Hey Max, On Sat, 10 Jul 2021, 11:09 Max Semenik, <maxsem.wiki@gmail.com> wrote:
> I've been thinking about extending PHP's cast syntax to user-defined types, > e.g. not only (int)$foo but also (MyClass)$foo. Currently, a T_STRING in > parentheses is always treated as a constant - would it be acceptable to > hijack this syntax when used in unary operation context, i.e. "(" T_STRING > ")" expr? If not, any other alternatives? >
How is this any better than `MyThing::fromStuff(...)`, with proper tested and domain-specific semantics?

[ ]

5 years ago
IMHO, This is suger which leads to diabetes. It adds pure implicitness without any payback. To convert object into something else add appropriate method. On Sat, Jul 10, 2021, 3:07 PM Marco Pivetta <ocramius@gmail.com> wrote:

David Rodrigues

5 years ago
I really like the casting syntax, but I think it is difficult to be approved, at first, because there is already a "clearer" method for doing something like that. Which for me, particularly, does not invalidate an alternative. My suggestion is to create a magic method like __cast(AllowedTypes $in): self. Eg. __cast(string|int $in): BigNumber. Where AllowedTypes are the classes allowed to be casted from (or "mixed"). So: $bn = (BigNumber) '123'; $date = (Carbon) $date; $timestamp = (Carbon) (int) $date; I also wanted it to be possible to use a nullable cast, but the idea didn't go forward (even though there were no alternatives). :( Atenciosamente, David Rodrigues Em sáb., 10 de jul. de 2021 às 06:10, Max Semenik <maxsem.wiki@gmail.com> escreveu:

Unnamed Person

5 years ago
I like Rust's From and TryFrom traits, which allow types to define conversions into or from other types. The From trait always succeeds, and TryFrom can fail. However, it's not a "cast" -- the user calls an `into`/`try_into` or `from`/`try_from` methods. I would be supportive of defining official ways to convert types from one to another, not necessarily casts, though. I think these qualities are important: 1. Would have failable and non-failable variants. It's nice to know that a particular conversion cannot or can fail. 2. It should be amenable to static analysis. Tools should be able to warn about missing paths for handling failable conversions, or that conversion from A to B is possible but A to C is not. I'm not sure how to technically achieve this for PHP. I don't think any casting proposal I've seen meets these characteristics, so I would personally vote no on them.

Larry Garfield

5 years ago
On Sun, Jul 11, 2021, at 5:30 PM, Levi Morrison via internals wrote:
> I like Rust's From and TryFrom traits, which allow types to define > conversions into or from other types. The From trait always succeeds, > and TryFrom can fail. However, it's not a "cast" -- the user calls an > `into`/`try_into` or `from`/`try_from` methods. > > I would be supportive of defining official ways to convert types from > one to another, not necessarily casts, though. I think these qualities > are important: > > 1. Would have failable and non-failable variants. It's nice to know > that a particular conversion cannot or can fail. > 2. It should be amenable to static analysis. Tools should be able to > warn about missing paths for handling failable conversions, or that > conversion from A to B is possible but A to C is not. > > I'm not sure how to technically achieve this for PHP. I don't think > any casting proposal I've seen meets these characteristics, so I would > personally vote no on them.
What are the use cases for integrated object to object (ie, class to class) conversion in PHP? Rust's type system is a very different beast, so its casting logic makes more sense there. I'm not entirely clear on what the use case is in PHP. When would that be superior to "just write an asFoo() method and move on with life"? Scalar conversion I can see, but every time someone suggests adding siblings to __toString(), there's major pushback. --Larry Garfield

Mike Schinkel

5 years ago
> On Jul 11, 2021, at 7:19 PM, Larry Garfield <larry@garfieldtech.com> wrote: > > What are the use cases for integrated object to object (ie, class to class) conversion in PHP? > I'm not entirely clear on what the use case is in PHP. When would that be superior to "just write an asFoo() method and move on with life"?
Reading your question triggered the following thoughts. They are not use-case's per-se because I can't remember them, but I know I have felt the need for these each at least once, and especially the last one. Note that you cannot currently do any of these with an asFoo() method, at least not directly. 1. It would be nice to be able to create a new instance of a parent, grandparent class, etc. class given the properties of a child class instance. Maybe: class Progenitor { public $surname; function __construct($surname) { $this-> surname = $surname; } } class Offspring extends Progenitor {} $offspring = new Offspring('Schinkel'); $progenitor = clone $offspring asinstanceof Progenitor echo get_class($progenitor); // prints: Progenitor echo $progenitor->surname; // prints: Schinkel 2. Similarly it would be nice to be able to treat an object as an instance of its parent/grandparent/etc. where (parentof $instance) === $instance $progenitor = $offspring asinstanceof Progenitor echo get_class($progenitor); // prints: Progenitor echo $progenitor === $offspring ? 'frue' : 'false; // prints: true Note I have no idea if having two instances where references are equal but they are different classes would have unintended consequences, so this might not be a good idea, not sure. But it would be nice to be able to treat an object as its parent from time to time, if it is possible. 3. It would also be nice to create a child class starting with an instance of a parent as its base. See example for #5. It should work the same. 4. Similarly it might be nice to be able to (somehow) assign a child class' identity and properties to an instance of its parent/grandparent/etc. where (childof $instance) === $instance although I have absolutely no idea how it this would work syntactically. ??? 5. And probably the functionality I've wanted most in this area — which is admittedly only tangentially-related — is to be able to assign to $this so we could do something like clone an object to get an equivalent object. Or if case #1 or #2 above was possible, by cloning an instance of its parent/grandparent/etc. would initialize for that object. For example: class Foo { public $value; function __construct(int $value) { $this->value = $value; } function fromFoo(Foo $foo) { $this = clone $foo; // <-- assigning $this replaces all state to be equal to the cloned instance } } $f1 = new Foo(100); $f2 = new Foo(0); $f2->fromFoo($f1); echo $f2->value; // prints 100
> Scalar conversion I can see, but every time someone suggests adding siblings to __toString(), there's major pushback.
Look ma, no magic methods! :-) -Mike

Unnamed Person

5 years ago
On Sun, Jul 11, 2021 at 5:19 PM Larry Garfield <larry@garfieldtech.com> wrote:
> > On Sun, Jul 11, 2021, at 5:30 PM, Levi Morrison via internals wrote: > > I like Rust's From and TryFrom traits, which allow types to define > > conversions into or from other types. The From trait always succeeds, > > and TryFrom can fail. However, it's not a "cast" -- the user calls an > > `into`/`try_into` or `from`/`try_from` methods. > > > > I would be supportive of defining official ways to convert types from > > one to another, not necessarily casts, though. I think these qualities > > are important: > > > > 1. Would have failable and non-failable variants. It's nice to know > > that a particular conversion cannot or can fail. > > 2. It should be amenable to static analysis. Tools should be able to > > warn about missing paths for handling failable conversions, or that > > conversion from A to B is possible but A to C is not. > > > > I'm not sure how to technically achieve this for PHP. I don't think > > any casting proposal I've seen meets these characteristics, so I would > > personally vote no on them. > > What are the use cases for integrated object to object (ie, class to class) conversion in PHP? Rust's type system is a very different beast, so its casting logic makes more sense there. I'm not entirely clear on what the use case is in PHP. When would that be superior to "just write an asFoo() method and move on with life"? > > Scalar conversion I can see, but every time someone suggests adding siblings to __toString(), there's major pushback. > > --Larry Garfield > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php >
The features I was referring to are not casting; casting does exist in Rust but this is not it. Rather, it's a generic way to convert objects. It's nice to have a standard way to do it, instead of every project doing something bespoke. It also can enable some use cases that aren't relevant in PHP (yet) such as taking any generic type which can be converted into a T, because you only want to do that transformation conditionally on something else.

Larry Garfield

5 years ago
On Mon, Jul 12, 2021, at 9:26 AM, Levi Morrison via internals wrote:
> > What are the use cases for integrated object to object (ie, class to class) conversion in PHP? Rust's type system is a very different beast, so its casting logic makes more sense there. I'm not entirely clear on what the use case is in PHP. When would that be superior to "just write an asFoo() method and move on with life"? > > > > Scalar conversion I can see, but every time someone suggests adding siblings to __toString(), there's major pushback. > > > > --Larry Garfield > > > > -- > > PHP Internals - PHP Runtime Development Mailing List > > To unsubscribe, visit: https://www.php.net/unsub.php > > > > The features I was referring to are not casting; casting does exist in > Rust but this is not it. Rather, it's a generic way to convert > objects. It's nice to have a standard way to do it, instead of every > project doing something bespoke. It also can enable some use cases > that aren't relevant in PHP (yet) such as taking any generic type > which can be converted into a T, because you only want to do that > transformation conditionally on something else.
So, like David suggested, some standardized version of $foo->as($type), which then returns an instance of $type? I... don't think I've ever done that often enough to justify a standard feature for it. When have you run into it? That also seems quite different from what Max is talking about: On Mon, Jul 12, 2021, at 9:54 AM, Max Semenik wrote:
> I was thinking of something akin to many compiled languages' approach of > "consider this expression is now of that type, and throw an exception if > it's not". An example of this approach from Benjamin's proposal of old^ > > $service = (EmailService) $diContainer->get('email.service'); > > Instead of > > /** @var EmailService $service */ > $service = $diContainer->get('email.service'); > if (!$service instanceof EmailService) { > throw new TypeError('Expected instance of EmailService, ...'); > }
Hm, that's a different creature. I... would be probably OK with something in that direction, though I wouldn't work on it myself. I think what you're describing here is more of a type assertion. "Assert that this variable is of type X, otherwise bail." So, some kind of non-disableable (or maybe disableable?) shorthand for `assert($foo instanceof Bar)`. --Larry Garfield

Mike Schinkel

5 years ago
> On Jul 12, 2021, at 11:00 AM, Larry Garfield <larry@garfieldtech.com> wrote: > x > On Mon, Jul 12, 2021, at 9:54 AM, Max Semenik wrote: > >> I was thinking of something akin to many compiled languages' approach of >> "consider this expression is now of that type, and throw an exception if >> it's not". An example of this approach from Benjamin's proposal of old^ >> >> $service = (EmailService) $diContainer->get('email.service'); >> >> Instead of >> >> /** @var EmailService $service */ >> $service = $diContainer->get('email.service'); >> if (!$service instanceof EmailService) { >> throw new TypeError('Expected instance of EmailService, ...'); >> } > > Hm, that's a different creature. I... would be probably OK with something in that direction, though I wouldn't work on it myself. I think what you're describing here is more of a type assertion. "Assert that this variable is of type X, otherwise bail." So, some kind of non-disableable (or maybe disableable?) shorthand for `assert($foo instanceof Bar)`.
Regarding prior art on type assertion, the syntax Go uses is `value.(type)` so using a similar approach in PHP might look like this (I'm spitballing by using the double colon as a sigil but it could anything that doesn't conflict with existing usage, whatever those options are): $service = $diContainer->get('email.service')::(EmailService); Additionally in Go a type assertion can return a second value which is boolean telling if the type assertion succeeded. Not having this would effectively moot the benefit to a type assertion if you had to wrap with try{}catch{} in case it failed. $service, $okay = $diContainer->get('email.service')::(EmailService); if (!$ok) { echo 'Not an EmailService.'; } #fwiw -Mike

Max Semenik

5 years ago
On Mon, Jul 12, 2021 at 8:01 PM Mike Schinkel <mike@newclarity.net> wrote:
> Additionally in Go a type assertion can return a second value which is > boolean telling if the type assertion succeeded. Not having this would > effectively moot the benefit to a type assertion if you had to wrap with > try{}catch{} in case it failed. >
Not necessarily: if ($obj instanceof MyClass) { // We know its type here } else { // Try something else }
-- Best regards, Max Semenik

Mike Schinkel

5 years ago
> On Jul 12, 2021, at 1:56 PM, Max Semenik <maxsem.wiki@gmail.com> wrote: > > On Mon, Jul 12, 2021 at 8:01 PM Mike Schinkel <mike@newclarity.net <mailto:mike@newclarity.net>> wrote: > Additionally in Go a type assertion can return a second value which is boolean telling if the type assertion succeeded. Not having this would effectively moot the benefit to a type assertion if you had to wrap with try{}catch{} in case it failed. > > Not necessarily: > > if ($obj instanceof MyClass) { > // We know its type here > } else { > // Try something else > }
Well there you go. It seems you have just illustrated why in reality we really do not need type casting/assertions in PHP given its current features, because we already have what we need. -Mike

Lynn

5 years ago
On Mon, Jul 12, 2021 at 10:20 PM Mike Schinkel <mike@newclarity.net> wrote:
> It seems you have just illustrated why in reality we really do not need > type casting/assertions in PHP given its current features, because we > already have what we need. >
That's not an argument I agree with, as it would invalidate the need for short closures, null coalesce, constructor property promotion, etc. Continuing on the previous example: ```php $service = $container->get(SomeService::class); assert($service instanceof SomeService); // could be replaced with $container->get<SomeService>(); // or in case of multiple instances: $container->get<SomeService>('the.service.alias'); // perhaps the service is optional $container->get<?SomeService>(); ```

Max Semenik

5 years ago
On Mon, Jul 12, 2021 at 11:20 PM Mike Schinkel <mike@newclarity.net> wrote:
> It seems you have just illustrated why in reality we really do not need > type casting/assertions in PHP given its current features, because we > already have what we need. >
Heh, nope - you asked for handling of such cases without exceptions, I obliged. My plans were around different usage scenarios. In any case, I'm not making a proposal at this point, I'm just enquiring about syntax. If I ever get something proposable done, I'll make a formal RFC.
-- Best regards, Max Semenik

Max Semenik

5 years ago
I was planning to propose something closer to On Mon, Jul 12, 2021 at 2:19 AM Larry Garfield <larry@garfieldtech.com> wrote:
> What are the use cases for integrated object to object (ie, class to > class) conversion in PHP? Rust's type system is a very different beast, so > its casting logic makes more sense there. I'm not entirely clear on what > the use case is in PHP. When would that be superior to "just write an > asFoo() method and move on with life"? > > Scalar conversion I can see, but every time someone suggests adding > siblings to __toString(), there's major pushback. >
I was thinking of something akin to many compiled languages' approach of "consider this expression is now of that type, and throw an exception if it's not". An example of this approach from Benjamin's proposal of old^ $service = (EmailService) $diContainer->get('email.service'); Instead of /** @var EmailService $service */ $service = $diContainer->get('email.service'); if (!$service instanceof EmailService) { throw new TypeError('Expected instance of EmailService, ...'); }
-- Best regards, Max Semenik

Nikita Popov

5 years ago
On Sat, Jul 10, 2021 at 11:10 AM Max Semenik <maxsem.wiki@gmail.com> wrote:
> I've been thinking about extending PHP's cast syntax to user-defined types, > e.g. not only (int)$foo but also (MyClass)$foo. Currently, a T_STRING in > parentheses is always treated as a constant - would it be acceptable to > hijack this syntax when used in unary operation context, i.e. "(" T_STRING > ")" expr? If not, any other alternatives? >
To answer your question without commenting on the wider discussion: No, it's not possible to use this syntax. C-style cast syntax is hopelessly ambiguous -- there's a reason why modern languages always pick a different syntax for type casts. To give a simple example, (MyClass) (CastArg) is already valid PHP syntax and performs a call to the function stored in constant MyClass, with the argument CastArg. Regards, Nikita