Type casting syntax

  115382
July 10, 2021 09:09 maxsem.wiki@gmail.com (Max Semenik)
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
  115383
July 10, 2021 11:05 benjamin.morel@gmail.com (Benjamin Morel)
> 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
  115384
July 10, 2021 11:10 olleharstedt@gmail.com (=?UTF-8?Q?Olle_H=C3=A4rstedt?=)
On Sat, 10 Jul 2021, 13:05 Benjamin Morel, 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
>
  115385
July 10, 2021 12:07 ocramius@gmail.com (Marco Pivetta)
Hey Max,

On Sat, 10 Jul 2021, 11:09 Max Semenik, 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?
>
  115388
July 11, 2021 18:26 zsidelnik@gmail.com (Eugene Sidelnyk)
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:

> Hey Max, > > On Sat, 10 Jul 2021, 11:09 Max Semenik, 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? > > > >
  115389
July 11, 2021 19:02 david.proweb@gmail.com (David Rodrigues)
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 wiki@gmail.com>
escreveu:

> 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 >
  115390
July 11, 2021 22:30 internals@lists.php.net ("Levi Morrison via internals")
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.
  115391
July 11, 2021 23:19 larry@garfieldtech.com ("Larry Garfield")
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
  115393
July 12, 2021 01:38 mike@newclarity.net (Mike Schinkel)
> 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
  115407
July 12, 2021 14:26 internals@lists.php.net ("Levi Morrison via internals")
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.
  115410
July 12, 2021 15:00 larry@garfieldtech.com ("Larry Garfield")
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
  115412
July 12, 2021 17:01 mike@newclarity.net (Mike Schinkel)
> 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
  115413
July 12, 2021 17:56 maxsem.wiki@gmail.com (Max Semenik)
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
  115415
July 12, 2021 20:20 mike@newclarity.net (Mike Schinkel)
> On Jul 12, 2021, at 1:56 PM, Max Semenik 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
  115416
July 12, 2021 20:28 kjarli@gmail.com (Lynn)
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(); // or in case of multiple instances: $container->get('the.service.alias'); // perhaps the service is optional $container->get(); ```
  115417
July 12, 2021 20:36 mike@newclarity.net (Mike Schinkel)
> On Jul 12, 2021, at 4:28 PM, Lynn <kjarli@gmail.com> wrote: > On Mon, Jul 12, 2021 at 10:20 PM Mike Schinkel <mike@newclarity.net <mailto: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();
In your hypothetical view here, what happens when the $container does not have SomeService to provide? Does it throw an Exception? -Mike
  115419
July 12, 2021 21:16 kjarli@gmail.com (Lynn)
On Mon, Jul 12, 2021 at 10:36 PM Mike Schinkel <mike@newclarity.net> wrote:

> > In your hypothetical view here, what happens when the $container does not > have SomeService to provide? Does it throw an Exception? > > Up to the developer that implements it. If the signature would be
`get(?string $id): T;` then I would assume an error should be thrown by PHP, or an Exception by the method, or handled by the developer to always return `T`. `?SomeException` as `T` could let the developer not throw an exception if needed and return `null` instead.
  115418
July 12, 2021 21:00 maxsem.wiki@gmail.com (Max Semenik)
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
  115409
July 12, 2021 14:54 maxsem.wiki@gmail.com (Max Semenik)
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
  115394
July 12, 2021 07:03 nikita.ppv@gmail.com (Nikita Popov)
On Sat, Jul 10, 2021 at 11:10 AM Max Semenik 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