Object Type Casting Reloaded

php.internals

Benjamin Morel

7 years ago
Hi internals, I'd like to revive an old discussion <https://externals.io/message/67131> about object type casting. The idea would be to allow (ClassName) casting: $service = (EmailService) $diContainer->get('email.service'); The above code would throw a TypeError if the value is not an instance of the given class. I see the following advantages: - Type safety: we can be sure that the value is of the correct type or that we'll get an Error. This syntax allows to fail early if the variable happens to not be of the expected type, and avoids much more verbose checks; - Static analysis: IDEs and static code analysis tools can now understand the type of the variable, without having to resort to `@var` annotations. These combine into a third advantage: readability. Today's equivalent of the above one-liner could be: /** @var EmailService $service */ $service = $diContainer->get('email.service'); if (! $service instanceof EmailService) { throw new TypeError('Expected instance of EmailService, ...'); } Which is a lot of boilerplate code that could be easily avoided by introducing this new syntax. Before moving forward and working on a formal RFC, I'd like to hear your thoughts: what's your early feeling about this? Did I miss other discussions around this subject? Are there any technical issues that come to mind? Could this feature help the upcoming JIT compiler produce more efficient machine code by knowing the type of the variable at compile time? etc. Note: "casting" might not be the perfect name here as what we're really doing is a type check, but this reuses the type casting syntax and resembles Java's object casting. Thank you, Ben

Larry Garfield

7 years ago
On Mon, Apr 22, 2019, at 4:47 PM, Benjamin Morel wrote:
> Hi internals, > > I'd like to revive an old discussion <https://externals.io/message/67131> about > object type casting. > > The idea would be to allow (ClassName) casting: > > $service = (EmailService) $diContainer->get('email.service'); > > The above code would throw a TypeError if the value is not an instance of > the given class. I see the following advantages: > > - Type safety: we can be sure that the value is of the correct type or that > we'll get an Error. This syntax allows to fail early if the variable > happens to not be of the expected type, and avoids much more verbose checks; > - Static analysis: IDEs and static code analysis tools can now understand > the type of the variable, without having to resort to `@var` annotations. > > These combine into a third advantage: readability. Today's equivalent of > the above one-liner could be: > > /** @var EmailService $service */ > $service = $diContainer->get('email.service'); > if (! $service instanceof EmailService) { > throw new TypeError('Expected instance of EmailService, ...'); > } > > Which is a lot of boilerplate code that could be easily avoided by > introducing this new syntax. > > Before moving forward and working on a formal RFC, I'd like to hear your > thoughts: what's your early feeling about this? Did I miss other > discussions around this subject? Are there any technical issues that come > to mind? Could this feature help the upcoming JIT compiler produce more > efficient machine code by knowing the type of the variable at compile time? > etc. > > Note: "casting" might not be the perfect name here as what we're really > doing is a type check, but this reuses the type casting syntax and > resembles Java's object casting. > > Thank you, > Ben
Hi Ben. First thought: I'm all for easy ways to be more type-explicit, so yay on the concept. Second thought: That said, how many use cases for that are there other than function boundaries, which we already have covered? I can think of two: foreach() loops and returns where you know the return type with more specificity than the method you're calling. Example: /** @var Foo $foo *// foreach ($arrayOfFoo as $foo) { $foo->bar(); } Example from PSR-14, in which you know the object you're getting back MUST be the same one that's passed in but dispatch() has no return type: /** @var Foo $event **/ $event = $dispatcher->dispatch(new Foo()); The second I can see being easily handled by this syntax. The former, how would that look? Third thought: Casting is the wrong name here, and feels also misleading as a syntax. (float)$anInt means "type coerce this thing into a float", which cannot error. You're suggesting (Foo)$bar to mean "if this isn't already a Foo, throw." That's a very different behavior semantic for the same syntax. Is that a land mine? I would expect (Foo)$bar to mean "recast $bar into an instance of Foo if possible, and error if not". Which... I suppose "is it already" is a subcase of that, but it's still not the behavior I'd expect from that syntax. --Larry Garfield

azjezz

7 years ago
Hello, Internals. I have been using HackLang for quite a while now and i believe they have a better solution for this, and it would awesome to see it in PHP, the `as` operator. see : https://docs.hhvm.com/hack/expressions-and-operators/as --- when i see : ``` $foo = (Foo) $object; ``` i think that `$object` is going to be converted to an instance of `Foo`, not ensure that its an instance of `Foo`. Hack `as` operator ``` $foo = $object as Foo; // or $object as Foo; // use `$object` now, known that its an instance of `Foo` ```
> a `TypeAssertionException` expect would be throw if `$object` is not `Foo`.
Sent with ProtonMail Secure Email. ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ On Monday, April 22, 2019 11:03 PM, Larry Garfield <larry@garfieldtech.com> wrote:

Benjamin Morel

7 years ago
Hi Azjezz, thanks for jumping in!
> I have been using HackLang for quite a while now and i believe they have
a better solution for this, and it would awesome to see it in PHP, the `as` operator. If I understand correctly, `as` is an operator that performs type checks but never casts like () does. This looks like a serious candidate for an alternative syntax to the one I proposed! - Ben

azjezz

7 years ago
Hello Ben. yes, i have made a gist with a simple example to show the `as` operator usage in hack + HHVM ( 4.1.0 ) output. see : https://gist.github.com/azjezz/03955ff2b009f1ced22ce68c9a862847 Sent with [ProtonMail](https://protonmail.com) Secure Email. ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ On Monday, April 22, 2019 11:50 PM, Benjamin Morel <benjamin.morel@gmail.com> wrote: > Hi Azjezz, thanks for jumping in! > >> I have been using HackLang for quite a while now and i believe they have a better solution for this, and it would awesome to see it in PHP, the `as` operator. > > If I understand correctly, `as` is an operator that performs type checks but never casts like () does. > This looks like a serious candidate for an alternative syntax to the one I proposed! > > - Ben

Stephen Reay

7 years ago
> On 23 Apr 2019, at 06:30, azjezz <azjezz@protonmail.com> wrote: > > Hello Ben. > > yes, i have made a gist with a simple example to show the `as` operator usage in hack + HHVM ( 4.1.0 ) output. > > see : https://gist.github.com/azjezz/03955ff2b009f1ced22ce68c9a862847 > > Sent with [ProtonMail](https://protonmail.com) Secure Email. > > ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ >> On Monday, April 22, 2019 11:50 PM, Benjamin Morel <benjamin.morel@gmail.com> wrote: >> >> Hi Azjezz, thanks for jumping in! >> >>> I have been using HackLang for quite a while now and i believe they have a better solution for this, and it would awesome to see it in PHP, the `as` operator. >> >> If I understand correctly, `as` is an operator that performs type checks but never casts like () does. >> This looks like a serious candidate for an alternative syntax to the one I proposed! >> >> - Ben
I like the idea but I find the syntax (both suggestions) less than great, given that there’s already “ensure a type” syntax, and both suggestions are already used for other things (casting and foreach) Is there some reason (eg ambiguities) a type can’t just be placed before a variable to ensure a type, similar to a function parameter or typed property? Eg string $foo = 'bar'; array $bar = getBar(); foreach($bar as int $k => string $v) {...} Cheers Stephen

azjezz

7 years ago
I think we would be talking about typed variables at that point. ( something you can do with PHP 7.4 http://github.com/azjezz/typed ) even though `as` is used with `foreach`, i don't think it would be an issue to use it for something else, but i think i will leave that to someone who's more familiar with the PHP parser than me. Cheers. Sent with ProtonMail Secure Email. ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ On Tuesday, April 23, 2019 1:43 AM, Stephen Reay <php-lists@koalephant.com> wrote:

Stephen Reay

7 years ago
> On 23 Apr 2019, at 07:50, azjezz <azjezz@protonmail.com> wrote: > > I think we would be talking about typed variables at that point. > > > ( something you can do with PHP 7.4 http://github.com/azjezz/typed ) > > even though `as` is used with `foreach`, i don't think it would be an issue to use it for something else, but i think i will leave that to someone who's more familiar with the PHP parser than me. > > > Cheers. > > Sent with ProtonMail Secure Email. > > ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ > On Tuesday, April 23, 2019 1:43 AM, Stephen Reay <php-lists@koalephant.com> wrote: > >>> On 23 Apr 2019, at 06:30, azjezz azjezz@protonmail.com wrote: >>> Hello Ben. >>> yes, i have made a gist with a simple example to show the `as` operator usage in hack + HHVM ( 4.1.0 ) output. >>> see : https://gist.github.com/azjezz/03955ff2b009f1ced22ce68c9a862847 >>> Sent with ProtonMail Secure Email. >>> ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ >>> >>>> On Monday, April 22, 2019 11:50 PM, Benjamin Morel benjamin.morel@gmail.com wrote: >>>> Hi Azjezz, thanks for jumping in! >>>> >>>>> I have been using HackLang for quite a while now and i believe they have a better solution for this, and it would awesome to see it in PHP, the `as` operator. >>>> >>>> If I understand correctly, `as` is an operator that performs type checks but never casts like () does. >>>> This looks like a serious candidate for an alternative syntax to the one I proposed! >>>> >>>> - Ben >> >> I like the idea but I find the syntax (both suggestions) less than great, given that there’s already “ensure a type” syntax, and both suggestions are already used for other things (casting and foreach) >> >> Is there some reason (eg ambiguities) a type can’t just be placed before a variable to ensure a type, similar to a function parameter or typed property? >> >> Eg >> >> string $foo = 'bar'; >> array $bar = getBar(); >> foreach($bar as int $k => string $v) {...} >> >> Cheers >> >> Stephen > >
I’m talking about exactly the same functionality that exists with method parameters - constraining the type it must conform to when it is declared. Nothing more nothing less. Using `as` means trying to add type constraints to an iterable in the foreach would be ridiculously confusing: foreach ( $foo as $K as int => $v as string) {...} And the cast syntax re-use means you’d not be able to use the object pseudotype (or you’d have to remove casting to object) Cheers Stephen

Marcos Passos

7 years ago
Such a feature would be very helpful to ensure type safety! A big +1! On Mon, Apr 22, 2019 at 19:51 Benjamin Morel <benjamin.morel@gmail.com> wrote:

Benjamin Morel

7 years ago
Hi Larry, thanks for the early feedback!
> First thought: I'm all for easy ways to be more type-explicit, so yay on
the concept. Glad to hear that!
> Second thought: (...) how many use cases for that are there other than
function boundaries, which we already have covered? I chose an example where function boundaries are typically not covered: dependency injection containers, which have some generic `get()` method that can return any type. Another one is ORMs: Doctrine's EntityManager::find(), for example, returns a generic `object`.
> The second I can see being easily handled by this syntax. The former,
how would that look? I guess it could be used this way: foreach ($arrayOfFoo as $foo) { ((Foo) $foo)->bar(); } Although this use case is not the primary motivation for this feature, which is not meant to replace all object type hints.
> Third thought: Casting is the wrong name here, and feels also misleading
as a syntax. I suspected this would come up, hence the final note; I'm open to suggestions for alternatives. Please note that Java's object casting uses the same syntax, and follows the same semantics of throw-if-not-instance-of. The difference, if I'm not mistaken, is that you can type-hint an object to more generic interface or parent class, or, on the contrary, type-hint it to a more specific sub-class or concrete class to make its members accessible; this part is obviously not needed in PHP as the VM does not care what the type is before attempting to access one of its members. But I'm not sure if the fact that we miss the "conversion" part is enough to justify a change in naming or syntax compared to the Java equivalent? - Ben On Tue, 23 Apr 2019 at 00:03, Larry Garfield <larry@garfieldtech.com> wrote:

Sebastian Bergmann

7 years ago
Am 22.04.2019 um 23:47 schrieb Benjamin Morel:
> These combine into a third advantage: readability. Today's equivalent of > the above one-liner could be: > > /** @var EmailService $service */ > $service = $diContainer->get('email.service'); > if (! $service instanceof EmailService) { > throw new TypeError('Expected instance of EmailService, ...'); > }
Today's equivalent is, at least for me, is this one-liner: assert($service instanceof EmailService); This way the IDE knows what type $service is supposed to be and there will be an exception at runtime (given the appropriate configuration) when this is not the case. Personally, I prefer hand-written factories that have an explicit createEmailService() method with a :EmailService return type declaration, for example, over the implicitness of a dependency injection container as the latter disguises and obscures dependencies.

M. W. Moe

7 years ago
Hello, Sebastian yes; interface or abstract have been somehow created for that purpose; even if I find people abusing of those constructs; now if your container is generalized and represents dynamic data; for instance like a Message class it could hold dynamic data types; let say: // abstract public offsetGet <https://www.php.net/manual/en/arrayaccess.offsetget.php> ( mixed <https://www.php.net/manual/en/language.pseudo-types.php#language.types.mixed> $offset ) : mixed <https://www.php.net/manual/en/language.pseudo-types.php#language.types.mixed> $service = $diContainer['email.service'] of ?EmailService; // else throw TypeError(this type instead of null or EmailService) On Mon, Apr 22, 2019 at 10:07 PM Sebastian Bergmann <sebastian@php.net> wrote:

Benjamin Morel

7 years ago
Hi all, thanks for the valuable feedback! @Stephen Reay:
> I like the idea but I find the syntax (both suggestions) less than
great, given that there’s already “ensure a type” syntax, and both suggestions are already used for other things (casting and foreach) Note that we already use `as` in other contexts than foreach: in imports (`use Foo as Bar`) and in traits (`use SomeTrait { foo as bar; }`) , so I think that reusing the keyword for yet another use case is OK, as long as it does not conflict.
> Is there some reason (eg ambiguities) a type can’t just be placed before
a variable to ensure a type, similar to a function parameter or typed property?
> string $foo = 'bar'; > ... > I’m talking about exactly the same functionality that exists with method
parameters - constraining the type it must conform to when it is declared. Nothing more nothing less. I would personnally read this as "this variable is now strongly typed, INCLUDING later assignments" (which would belong to another feature / discussion), but as you have later clarified that this is not what you had in mind, I think that this syntax is then even more confusing than the other proposals: Foo $foo = new Foo(); $foo = 123; // this would work?
> Using `as` means trying to add type constraints to an iterable in the
foreach would be ridiculously confusing:
> foreach ( $foo as $K as int => $v as string) {...}
Please note that my proposal is specific to class/interface names, not other types for now (I could consider broadening it if people are interested in the suggested "as" syntax from Hack). Anyway with the two syntaxes suggested so far, you would not inline it in the foreach, but explicitly write: foreach ($foos as $foo) { $foo = (Foo) $foo; // or $foo = $foo as Foo; } @Andreas Hennings:
> If I remember correctly, an object pointer (object variable) in Java has
a type independent of the object it points to. The pointer type restricts the range of object types that can be pointed to from the variable.
> You can cast the pointer type (variable type) to let a variable of
different object pointer type store the same object, as long as the type is compatible.
> In PHP, variables have no type on language level, so casting would indeed
be a misleading concept. Unless we want to change this. You have a point for sure, but as I said above, I don't feel like this makes it incompatible with casting semantics. In Java, casting to a class/interface name: 1. ensures that the variable holds an instance of this class/interface 2. changes the pointer type, which affects later usage of the object In PHP, you'd just scratch point #2 which is irrelevant. Is this a good enough reason to stop using the casting syntax? I'm not sure. @Sebastian Bergmann:
> Today's equivalent is, at least for me, is this one-liner: > assert($service instanceof EmailService);
Let's be picky: this is actually a two-liner, a second line after the variable declaration ;-)
> This way the IDE knows what type $service is supposed to be and there
will
> be an exception at runtime (given the appropriate configuration) when
this
> is not the case.
That's a fair point regarding the IDE. The exception at runtime however, becomes strongly dependent on PHP configuration. We're also losing the advantage of an explicit language construct, that I suspect could be later used by the JIT compiler to generate more efficient machine code by knowing the type of the variable at this point at compile time (I'd love to get feedback on this point from Dmitry).
> Personally, I prefer hand-written factories that have an explicit > createEmailService() method with a :EmailService return type declaration, > for example, over the implicitness of a dependency injection container as > the latter disguises and obscures dependencies.
We're entering the realm of patterns here; I'm a big fan of IoC myself and even though I use it a lot in my apps, I still find myself calling `$diContainer->get()` more often than not, for example in quick prototypes or CLI scripts. Same thing for ORMs: you may have a full-blown repository for each entity with a strongly typed `find() : ClassName` method, but you may also need to call `$entityManager->find()` directly from time to time. There are also plenty of other use cases in PHP, where you get a variable from a losely typed source (array or whatever), but somehow know that it should be of a given type. - Ben On Tue, 23 Apr 2019 at 07:58, M. W. Moe <mo.mu.wss@gmail.com> wrote:

Stephen Reay

7 years ago
> On 23 Apr 2019, at 16:03, Benjamin Morel <benjamin.morel@gmail.com> wrote: > > > I like the idea but I find the syntax (both suggestions) less than great, given that there’s already “ensure a type” syntax, and both suggestions are already used for other things (casting and foreach) > > Note that we already use `as` in other contexts than foreach: in imports (`use Foo as Bar`) and in traits (`use SomeTrait { foo as bar; }`) , so I think that reusing the keyword for yet another use case is OK, as long as it does not conflict. > > > Is there some reason (eg ambiguities) a type can’t just be placed before a variable to ensure a type, similar to a function parameter or typed property? > > string $foo = 'bar'; > > ... > > I’m talking about exactly the same functionality that exists with method parameters - constraining the type it must conform to when it is declared. Nothing more nothing less. > > I would personnally read this as "this variable is now strongly typed, INCLUDING later assignments" (which would belong to another feature / discussion), but as you have later clarified that this is not what you had in mind, I think that this syntax is then even more confusing than the other proposals: > > Foo $foo = new Foo(); > $foo = 123; // this would work? > > > Using `as` means trying to add type constraints to an iterable in the foreach would be ridiculously confusing: > > foreach ( $foo as $K as int => $v as string) {...} > > Please note that my proposal is specific to class/interface names, not other types for now (I could consider broadening it if people are interested in the suggested "as" syntax from Hack). > Anyway with the two syntaxes suggested so far, you would not inline it in the foreach, but explicitly write: > > foreach ($foos as $foo) { > $foo = (Foo) $foo; > // or > $foo = $foo as Foo; > } > >
Hi Ben, My objection to `as` is that it specifically prevents (or makes very confusing) the `foreach` use I mentioned. There’s no variables involved in imports/traits, and neither of those relates to foreach. As for the `string $foo = $bar->getFoo();` ‘looking strongly typed’ and wondering if it will allow re-declaration - do you think the same when you see `function (string $foo) { $foo = explode(‘ ‘, $foo); }` ? Functions have allowed type hints since 5.0 - nearly 15 years ago, and nobody seems to be confused if those variables can be redeclared. Whether you *should* redeclare them to a different value is something you need to decide for yourself. As for limiting this to class/interface names - why would you do that? It took YEARS to get scalar type hints for function parameters, why would you not include them from the start on anything new that pertains to essentially the same functionality? I can’t even begin to comprehend how re-assigning a variable to itself (even if it is a no-op internally, it *appears* to re-assign) to do a type check seems like a good option. Cheers Stephen

Nikita Popov

7 years ago
On Mon, Apr 22, 2019 at 11:48 PM Benjamin Morel <benjamin.morel@gmail.com> wrote:
> Hi internals, > > I'd like to revive an old discussion <https://externals.io/message/67131> > about > object type casting. > > The idea would be to allow (ClassName) casting: > > $service = (EmailService) $diContainer->get('email.service'); > > The above code would throw a TypeError if the value is not an instance of > the given class. I see the following advantages: > > - Type safety: we can be sure that the value is of the correct type or that > we'll get an Error. This syntax allows to fail early if the variable > happens to not be of the expected type, and avoids much more verbose > checks; > - Static analysis: IDEs and static code analysis tools can now understand > the type of the variable, without having to resort to `@var` annotations. > > These combine into a third advantage: readability. Today's equivalent of > the above one-liner could be: > > /** @var EmailService $service */ > $service = $diContainer->get('email.service'); > if (! $service instanceof EmailService) { > throw new TypeError('Expected instance of EmailService, ...'); > } > > Which is a lot of boilerplate code that could be easily avoided by > introducing this new syntax. > > Before moving forward and working on a formal RFC, I'd like to hear your > thoughts: what's your early feeling about this? Did I miss other > discussions around this subject? Are there any technical issues that come > to mind? Could this feature help the upcoming JIT compiler produce more > efficient machine code by knowing the type of the variable at compile time? > etc. > > Note: "casting" might not be the perfect name here as what we're really > doing is a type check, but this reuses the type casting syntax and > resembles Java's object casting. > > Thank you, > Ben >
Without commenting on the rest of the proposal: It's not possible to use (ClassName) as a cast syntax, because it is ambiguous. For example (Foo) [$x] is already valid syntax (fetch constant Foo and take index $x), or (Foo) +$bar, etc. The only reason why (int) etc are okay is because we treat the whole (int) as a single token, something we can't do in general (because it would break foo(Foo)). Nikita

azjezz

7 years ago
Hello Nikita, I would love to hear your opinion on the `as` syntax from Hack, and whether it can be used in PHP the same way or would it be an issue. Cheers, - Saif Sent with ProtonMail Secure Email. ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ On Tuesday, April 23, 2019 10:16 AM, Nikita Popov <nikita.ppv@gmail.com> wrote:

Michał Brzuchalski

7 years ago
wt., 23 kwi 2019 o 11:17 Nikita Popov <nikita.ppv@gmail.com> napisał(a):
> ... > Without commenting on the rest of the proposal: It's not possible to use > (ClassName) as a cast syntax, because it is ambiguous. For example (Foo) > [$x] is already valid syntax (fetch constant Foo and take index $x), or > (Foo) +$bar, etc. >
Wouldn't that be possible to differentiate consts with class names if we merge symbol tables and allow only one symbol with the same name? I know this is huge BC break but AFAIK in the past, there was a discussion about merging symbol tables.
-- regards / pozdrawiam, -- Michał Brzuchalski about.me/brzuchal brzuchalski.com

Andrey Hristov

7 years ago
Hi, maybe with short closures this will be pretty easy to implement by injecting a short closure that expects the return type to be of the type to be casted and to return it. Something in the like of : $service = (fn(EmailService $s):EmailService => $s ;)($diContainer->get('email.service')); Not that I support pulling services from a container most often being an anti-pattern. Cheers, Andrey On 23.04.19 г. 0:47 ч., Benjamin Morel wrote:

Dan Ackroyd

7 years ago
HI Benjamin, Similar to the nullable casting idea, you're taking a problem caused by your own code/framework, and then thinking it should be solved in core, when a simple solution is available in user-land. If you changed what you currently have: $service = $diContainer->get('email.service'); to also take the expected class: $service = $diContainer->get('email.service', EmailService::class); And then check inside your 'DI container' whether the expected type is returned, this solves the problem without needing new syntax. btw I'm sure you're already aware of it, but this is using a 'dependency injector' as a service locator. If your current DI library isn't powerful enough for you, rather than abusing it like this, I'd recommend looking at a different one, like https://github.com/rdlowrey/Auryn Also, similar:
> > By the way, this RFC is a special case of something that could be far > more generic. If it was possible to register callbacks to be used when > casting, ...
Apparently this might not be possible as it's ambiguous....which is a shame. cheers Dan Ack On Mon, 22 Apr 2019 at 22:47, Benjamin Morel <benjamin.morel@gmail.com> wrote:

azjezz

7 years ago
Hello Dan, I don' think this a problem relating to just one use case, some PHP builtin functions have weird union return types, where static analysis tools would warn you about the return type being `string|bool`, when you are expecting `string`. using type constrain : ``` $foo = substr($foo, 1, 3) as string; // there's no need to check if `$foo` is false here. ``` Cheers, - Saif Sent with ProtonMail Secure Email. ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ On Tuesday, April 23, 2019 11:33 AM, Dan Ackroyd <Danack@basereality.com> wrote:

Andrey Hristov

7 years ago
Hi, On 23.04.19 г. 13:44 ч., azjezz wrote:
> Hello Dan, > > I don' think this a problem relating to just one use case, some PHP builtin functions have weird union return types, where static analysis tools would warn you about the return type being `string|bool`, when you are expecting `string`. > > using type constrain : > ``` > $foo = substr($foo, 1, 3) as string; > // there's no need to check if `$foo` is false here. > ```
this is easily solvable with the following (considering strict_types is enabled) function tostr(string $in) : string { return $in; } $foo = tostr($foo); Put it in a convenience namespace and that's it. Cheers, Andrey

azjezz

7 years ago
Hello Andery. yes that's possible, but would need a function for each type, or we can do : https://3v4l.org/6B3hA but this is still not type-safe, as we won't be able to type hint it correctly without using generics + `typename` type that exists in Hack. using generics + `typename` : `function check<T>(mixed $var, typename<T> $type): T` Cheers, - Saif Sent with ProtonMail Secure Email. ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ On Tuesday, April 23, 2019 11:51 AM, Andrey Hristov <php@hristov.com> wrote:

Benjamin Morel

7 years ago
@Nikita Popov <nikita.ppv@gmail.com>
> It's not possible to use (ClassName) as a cast syntax, because it is
ambiguous. Aw. I did not think about a possible conflict here, thanks for bringing this up! @Stephen Reay <php-lists@koalephant.com>
> My objection to `as` is that it specifically prevents (or makes very
confusing) the `foreach` use I mentioned. There’s no variables involved in imports/traits, and neither of those relates to foreach. I'm sorry, I still don't understand what the issue with foreach is? As I said before, the proposed syntax does not aim to be used inside the foreach () parentheses, but rather as an explicit declaration inside the foreach {} block.
> As for the `string $foo = $bar->getFoo();` ‘looking strongly typed’ and
wondering if it will allow re-declaration - do you think the same when you see `function (string $foo) { $foo = explode(‘ ‘, $foo); }` ? Functions have allowed type hints since 5.0 - nearly 15 years ago, and nobody seems to be confused if those variables can be redeclared. Whether you *should* redeclare them to a different value is something you need to decide for yourself. You have a point. I can't say why, but outside of the function declaration, it looks more like a static typing to me (and someone else had the same gut reaction earlier in this thread). I may just be plain wrong.
> As for limiting this to class/interface names - why would you do that? It
took YEARS to get scalar type hints for function parameters, why would you not include them from the start on anything new that pertains to essentially the same functionality? I'm definitely not against supporting other types, it's just that the original syntax I had in mind was using () casting, which was already covered for other types. Now if people like Hack's `as` syntax, it would probably make sense to generalize it indeed. @Dan Ackroyd <Danack@basereality.com>
> Similar to the nullable casting idea, you're taking a problem caused > by your own code/framework, and then thinking it should be solved in > core, when a simple solution is available in user-land. > ... > btw I'm sure you're already aware of it, but this is using a > 'dependency injector' as a service locator. If your current DI library > isn't powerful enough for you, rather than abusing it like this,
I think that the sudden focus on the specific case of DI container / service locator reveals a misunderstanding here. The example I chose is obviously responsible for that and this choice was perhaps infortunate: it didn't help convey the true meaning of the proposal. As I said earlier, I'm a heavy user of Dependency Injection AND Inversion of Control; my framework does not suffer from any such limitation: my controllers' and services' dependencies are explicitly declared and injected automatically, and no such code relies on fetching dependencies from the container directly. These classes are not even aware of the existence of the container, and not tied to the DI framework in any way. Also, my repositories are full blown, and encapsulate the ORM to return specific entities. YET, I also do some quick prototypes or CLI scripts that would not benefit from too much boilerplate, and may use the DI container or the ORM's entity manager directly. I also get objects from various losely typed data sources (functions returning mixed types, arrays, iterators of all sorts, etc.) that I expect to be of a given type, and that would benefit from the added *readability, type checking and static analysis capabilities* the current proposal offers. @Andrey Hristov <php@hristov.com>
> this is easily solvable with the following (considering strict_types is
enabled)
> function tostr(string $in) : string { return $in; }
This solves it for a single type, but you have to write a function for every class you want to enforce: function toEmailService(EmailService $service) : EmailService { return $service; } function toUser(User $user) : User { return $user; } As you can see, this gets ugly real quick. Your IDE and/or static analysis tool will also probably warn about "Expecting Foo, got mixed", whereas with an explicit cast/enforced-type-hint, the tooling would know that you're explicitly stating that this unknown typed value is an instance of Foo. - Ben On Tue, 23 Apr 2019 at 12:52, Andrey Hristov <php@hristov.com> wrote:

Mark Randall

7 years ago
On 23/04/2019 13:32, Benjamin Morel wrote:
> Aw. I did not think about a possible conflict here, thanks for bringing > this up!
It keeps coming back to the feature we've all been praying to the PHP Gods for... generics. function cast<type T>(object $obj): T { if ($obj instanceof T) { return $obj; } throw new InvalidArgumentException('Cast expected ' . T); } $x = cast<App.Object>($obj);
-- Mark Randall

Mark Randall

7 years ago
On 23/04/2019 14:39, Mark Randall wrote:
> It keeps coming back to the feature we've all been praying to the PHP > Gods for... generics.
Replying to myself (sorry). I just wanted to point out that the same syntax would also solve your other issue from the nullable casts thread: function ncast<type T>($value) ?T { if ($value === null) { return null; } if (settype($value, T) === false) { throw new InvalidArgumentException('Cannot convert to ' . T); } return $value; } ncast<int>(null); ncast<int>(12345); ncast<string>(null); ncast<string>('abcdef');
-- Mark Randall

Stephen Reay

7 years ago
> On 23 Apr 2019, at 19:32, Benjamin Morel <benjamin.morel@gmail.com> wrote: > > I'm sorry, I still don't understand what the issue with foreach is? As I said before, the proposed syntax does not aim to be used inside the foreach () parentheses, but rather as an explicit declaration inside the foreach {} block. >
And as I said before, I can’t understand how that is supposed to be better than just doing what works now, using instanceof to check the type inside the block: if (! $foo instanceof Foo) { throw new TypeError(…); } At least with the above, it’s obvious what is happening.

Benjamin Morel

7 years ago
@Stephen Reay <php-lists@koalephant.com>
> And as I said before, I can’t understand how that is supposed to be
better than just doing what works now, using instanceof to check the type inside the block:
> if (! $foo instanceof Foo) { throw new TypeError(…); } > At least with the above, it’s obvious what is happening.
It's exactly the purpose of the proposal: avoid this boilerplate. For the same reason you probably write: function test(Foo $foo, string $bar) {} and not: function test($foo, $bar) { if (! $foo instanceof Foo) throw new TypeError(); if (! is_string($bar)) throw new TypeError(); } We could have argued a few years ago that adding type-hints to PHP was unnecessary, for the same reason: if was possible to do these checks in userland. It's just much more readable and concise to let the engine perform the checks. The current proposal is to fill the gap of type checking / type hinting **inline**, when getting data from an losely typed source. - Ben

Stephen Reay

7 years ago
> On 23 Apr 2019, at 21:02, Benjamin Morel <benjamin.morel@gmail.com> wrote: > > It's exactly the purpose of the proposal: avoid this boilerplate. For the same reason you probably write: >
Replacing 1 line of “boiler plate” with a *different* line of less-obvious “boiler plate” is not an improvement IMO.
> The current proposal is to fill the gap of type checking / type hinting **inline**, when getting data from an losely typed source.
Parameter type hints use syntax that is clear, concise and reasonably common in other languages. Similarly, defining the type of a variable when it’s declared is clear and concise reasonably common in other languages. In languages without type hinting (e.g. JavaScript) - the “boiler plate” you’re trying to remove is also common, e.g. using `typeof` in JavaScript. Assigning a variable to itself, because you want to check its type is the weirdest concept I’ve heard in a long time. You want to add more capability to enforce type checking - I get that, and I share that goal. But when we already have pretty common, intuitive syntax to do so in one situation, I don’t see why you wouldn’t adopt the same syntax elsewhere. The typed properties RFC adopted the same syntax as parameter hints, even though they have some different implementation details (e.g. you can redeclare a function parameter to another type, you can’t with a typed class property). I can see that there would be pros and cons to either approach for typed local variables - but IMO *that* is the discussion to have. The initial RFC doesn’t even need to cover every possible scenario - it could literally just cover plain variable declarations. A later one could cover adding type hints to for example, foreach. By using syntax that’s already in-use, *and* doesn’t conflict with other *potential* points of implementation, we can hopefully end up with more options for type support in the language. Cheers Stephen

Benjamin Morel

7 years ago
@Stephen Reay <php-lists@koalephant.com>
> In languages without type hinting (e.g. JavaScript) - the “boiler plate”
you’re trying to remove is also common, e.g. using `typeof` in JavaScript. It's also very common to *not* check types in JavaScript... I assume the absence of type hints is partly responsible for this, and a reason why quite a lot of JS developers turn to typed preprocessors, such as TypeScript.
> Assigning a variable to itself, because you want to check its type is the
weirdest concept I’ve heard in a long time. I assume you're speaking of the foreach() example; sure, used this way, it looks like a self-assignment, but in most cases it's not, it's an inline, checked type-hint: // (assuming the 'as' syntax, now that the cast syntax has been buried) $foo = $entityManager->find(User::class, 123) as User; You're effectively adding, **inline and with just a few chars**, an additional type-hint and runtime check to the value. I wouldn't call this a no-op.
> You want to add more capability to enforce type checking - I get that,
and I share that goal. But when we already have pretty common, intuitive syntax to do so in one situation, I don’t see why you wouldn’t adopt the same syntax elsewhere. While I don't agree with you on many points, I appreciate that we share the same goal somehow, and hope that we can find common grounds. I would personally not be against variables typed on declaration, but even though this probably does not make sense, I'm somehow more comfortable with: function test(string $name) { $name = 123; } than: string $name = 'Ben'; $name = 123; I guess that I could be more comfortable with this approach if we had a switch, such as `declare(locked_types=1)`, that would enforce the type of the variable throughout its lifetime. This may come at a huge runtime cost, though. - Ben

Stephen Reay

7 years ago
> On 23 Apr 2019, at 21:54, Benjamin Morel <benjamin.morel@gmail.com> wrote: > > @Stephen Reay <php-lists@koalephant.com> > >> In languages without type hinting (e.g. JavaScript) - the “boiler plate” > you’re trying to remove is also common, e.g. using `typeof` in JavaScript. > > It's also very common to *not* check types in JavaScript... I assume the > absence of type hints is partly responsible for this, and a reason why > quite a lot of JS developers turn to typed preprocessors, such as > TypeScript. > >> Assigning a variable to itself, because you want to check its type is the > weirdest concept I’ve heard in a long time. > > I assume you're speaking of the foreach() example; sure, used this way, it > looks like a self-assignment, but in most cases it's not, it's an inline, > checked type-hint: > > // (assuming the 'as' syntax, now that the cast syntax has been buried) > $foo = $entityManager->find(User::class, 123) as User; > > You're effectively adding, **inline and with just a few chars**, an > additional type-hint and runtime check to the value. I wouldn't call this a > no-op. > >> You want to add more capability to enforce type checking - I get that, > and I share that goal. But when we already have pretty common, intuitive > syntax to do so in one situation, I don’t see why you wouldn’t adopt the > same syntax elsewhere. > > While I don't agree with you on many points, I appreciate that we share the > same goal somehow, and hope that we can find common grounds. I would > personally not be against variables typed on declaration, but even though > this probably does not make sense, I'm somehow more comfortable with: > > function test(string $name) { > $name = 123; > } > > than: > > string $name = 'Ben'; > $name = 123; > > I guess that I could be more comfortable with this approach if we had a > switch, such as `declare(locked_types=1)`, that would enforce the type of > the variable throughout its lifetime. This may come at a huge runtime cost, > though. > > - Ben
Hi Ben, I realise type checking in JS is not really common, my intention was to indicate that the suggested `$foo = $bar->foo() as Foo` is basically unheard of in any language besides Hack. As has been suggested and refuted many times - less code is not necessarily more readable. In this case, I still feel `$foo instanceof Foo || throwTypeError()` or even a 3-liner `if` to do the throw inline, is more readable and more obvious than the proposed syntax. Yes - because of the syntax you’re proposing, this feature could never realistically be added to `foreach` statements without making them hard to read at best, and possibly (I don’t know, I’m not familiar with the C internals) impossible. Given that type hinting the members of an iterable is something that has been discussed and rejected before, the ability to hint types of an iterable is a no-brainer to me. It’s also one of the two examples Larry came up with. As I understand it, you find `Foo $obj = $bar->getFoo();` to imply a fixed type for the variable $obj? I’ll just recap one more time, and then I’m not going to repeat this, because it’s surely getting boring for everyone else on the list. There is (as of 7.4) precedent for a typed “variable” that *is* locked to the type it’s declared with (class properties^), and for typed variables (function parameters) that are *not* locked to the type they're declared with. If this feature were added, it surely will follow the behaviour of one of those two: either they are or are not a fixed type, and one of the three cases will become the ‘exception’ developers need to remember when using typed variables/properties. ^: Yes I know class properties aren’t really the same as local variables, but they’re presented very similarly in syntax and behave mostly the same pre-7.4 I *assume* implementation wise it’s not even realistic to make local variables behave as 7.4+ typed class properties do (i.e. fixed type), but my point is that from a “do people want this” point of view, I believe the people who are actually likely to declare types on local variables, are *probably* quite unlikely to re-declare variables to a different type like that in most cases anyway. I’d also suggest that just adding local variable type hinting “now” (i.e. in 8.0 if an RFC passed) in a manner consistent with function parameter type hints, would mean that a later discussion/RFC could approach the concept of fixed-type variables (e.g using a `declare` option as you mentioned). Cheers Stephen