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
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, aT_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
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, aT_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
Hey Max,
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, aT_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?
IMHO, This is suger which leads to diabetes. It adds pure implicitness
without any payback. To convert object into something else add appropriate
method.
Hey Max,
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, aT_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?
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:
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, aT_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
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:
- Would have failable and non-failable variants. It's nice to know
that a particular conversion cannot or can fail. - 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.
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
orfrom
/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:
- Would have failable and non-failable variants. It's nice to know
that a particular conversion cannot or can fail.- 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
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.
- 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
- 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.
- 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.
- 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.
???
- 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
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
orfrom
/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:
- Would have failable and non-failable variants. It's nice to know
that a particular conversion cannot or can fail.- 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
--
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.
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
--
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:
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
x
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
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
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
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:
$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>();
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:
$service = $container->get(SomeService::class); assert($service instanceof SomeService); // could be replaced with $container->get<SomeService>();
In your hypothetical view here, what happens when the $container does not have SomeService to provide? Does it throw an Exception?
-Mike
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<T>(?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.
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
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
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, aT_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