Hi Stephen, Thank you for the follow up.
I am heading out for a week-long conference later today and not sure if I will have time to participate on the list for a while so I wanted to get a quick reply to you before I leave. > In terms of how I would see it working - In hindsight I should have been more clear what I meant when I said "working." A critical aspect of the proposal is the syntax for creating anonymous unions, e.g.: process(Guid|string $guid) vs. always requiring `GuidUnion` to be predefined before using it in a context like process(GuidUnion $guid). That is the part where I cannot see interfaces working, unless they are created magically too. But it there really any point to that? > For classes, I don’t know what the solution would be - and I’m honestly less > clear on how much demand there is for that level of predictable, > developer-controlled automatic casting anyway. I am getting that impression, at least from your response. That seems weird to me though given how many people on the list want a stricter language. To me this seemed like the best of both worlds, but I do get it. > Are you suggesting ... where a function to handle a date/time may for example > accept an integer or an instance of \DateTimeInterface and thus be given a > variable of either type and need to do whatever appropriate checks to use it, > instead it would set a type hint of e.g `DateOrTimestamp` which is a class > defining that it’s either an int or \DateTimeInterface via the `types` > keyword, and the original function would get an instance of that object, > regardless of whichever type is passed, and the engine would automatically > wrap/box the passed value in that class? Yes, you got it, except you did not mention the ability to create an anonymous union with `int|\ DateTimeInterface`, i.e. using the vertical bar where a type specifier can otherwise be used. So per the proposal this: function processGuid(string|Guid $guid) { ... } Would be equivalent to this, except the above union would not have an actual name that could be reused elsewhere, just like anonymous classes don't: class GuidUnion { types string|Guid; } function processGuid(GuidUnion $guid) { ... } > The example you provided doesn’t quite make sense to me even in the above > scenario, because it shows accepting a string scalar as a type, but then > somehow that value is an object with methods, not a scalar? Is that a further > misunderstanding on my point, or is that example meant to include a > hypothetical `GuidLike` ‘union class’ which has `types Guid|string` in it?. If I understand your confusion I think you are confused because I was showing the use of an anonymous union and the proposal proposes that a scalar passed to a function type hinted to be a union would receive the scalar "boxed" into a union object. This means in the function you access the desired scalar via accessor methods of the union object and not a scalar. Does that clarify? > Looking back at your original proposal’s examples, it becomes clearer with > the above understanding (if that’s what you meant), but it also seems even > _less_ intuitive to me now, even though I understand (well, I *think* I do) > what you’re actually suggesting, and I still don’t really see the benefits > you’re proposing. I am appreciating that it comes across as less intuitive. It is probably intuitive to me because what I am proposing is (almost) exactly how *empty* interfaces work in GoLang and I have been programming in my spare time in Go for a year now alongside my paid PHP duties. The question is, is this concept so unintuitive as to make it a non-starter for PHP, or is it like many concepts in programming; unintuitive when you first see them but once you learn them completely intuitive? That is a rhetorical question I cannot answer as a single individual; the arrive at an answer requires the feedback of many others. > If a method accepts a parameter with a union type of `GUID|string`, yes you > have to do some work to verify that the string is in fact a guid - possibly > converting it into an instance of GUID along the way. Which is part of what the proposal is trying to standard and streamline as there is no way to cast an object to a class in PHP, and thus no way to get a type-safe value as a known object. You can do this: $local_guid = $guid instanceof \Guid ? $guid : null; But it seems ro me so much more elegant — and readable — if you could just do this: $local_guid = $guid->toGuid(); > If a method has a signature using a ‘Union Class type’ which has a `types > GUID|string` keyword, you _still_ have to verify that the result of value() > (or toString()) is in fact a valid GUID Yes, and no. In the proposal $guid->toGuid() would return null, so no need to have to check if you can use the presence of null as an indicator. But if you did need to check that is why the proposal includes the $guid->type() method, that works well with `switch` statements; something we do not otherwise have in PHP because of the distinction between `gettype()` vs. `get_class()`. The proposal's approach means less potentially confusing boilerplate logic. As an aside, turning classes into first-class language elements would be super powerful and I would really like to see that, but that feels like too big of an ask for the PHP community so I have been thinking it would not be worth the effort to create such a proposal. > because the calling site has just given you a string, and the ‘magic’ has > boxed that up into an object.. so you can call a method to get back the same > value, right? Not sure I am following that question unless you are just confirming that when $guid->type() === 'string' that is_string( $guid->toString()) is true? One benefit to this approach I realize my proposal did not emphasize as that of a union becoming a first-class entity, with the ability to extend the base union when not using anonymous unions, and to add to or modify its methods. This is something we won't get if we simply use type aliases in Nikitia's proposal. But in hindsight maybe I am trying to propose types as first-class objects after all, using a use-case where there would be real tangible benefits vs. the general case? What I think I am recognizing is that while this functionality works brilliantly in Go it may not be of the nature that makes sense for the PHP world. Anyway, thank you again for following up. If I am unable to reply to any responses for the next week+ it will be because I am focusing on the conference I will be attending. -Mike > - because the calling site has just given you a string, and the ‘magic’ has > boxed that up into an object.. so you can call a method to get back the same > value, right? > On Sep 8, 2019, at 9:56 AM, Stephen Reay <php-li...@koalephant.com> wrote: > > Hi Mike, > > Sorry for the delay responding to this. > > So I would agree that magic methods are generally a less-obvious solution, > and interfaces are generally a better alternatives for new solutions. > > In terms of how I would see it working - the same way that implementing the > `Iterator` (or `IteratorAggregate`) interfaces allows a class to be iterated > using foreach, my thought (and im pretty sure I’ve seen a similar concept > suggested by others on internals before too) was that e.g. when passing an > object that implements the `stringable` interface to a type that expects a > string, it would convert it to such, even in strict mode, without warning or > error. The same could be used for any built in basic type (scalars and > arrays). > > Heck, a `Money` class could implement `stringable` (return a formatted > string), `intable` (return the amount in the minor currency unit - i.e. cents > for most dollar currencies) and `floatable` (return an approximation of the > amount as major.minor. > > Again - I know those names are not fantastic, but you get the idea. > > > For classes, I don’t know what the solution would be - and I’m honestly less > clear on how much demand there is for that level of predictable, > developer-controlled automatic casting anyway. > > I’m almost certain I’ve thus-far missed some point of what you were trying to > convey, but I _think_ I understand now… sort of. > > Are you suggesting that rather than (or as well as?), as per Nikita’s > proposal, where a function to handle a date/time may for example accept an > integer or an instance of \DateTimeInterface and thus be given a variable of > either type and need to do whatever appropriate checks to use it, instead it > would set a type hint of e.g `DateOrTimestamp` which is a class defining that > it’s either an int or \DateTimeInterface via the `types` keyword, and the > original function would get an instance of that object, regardless of > whichever type is passed, and the engine would automatically wrap/box the > passed value in that class? > > > The example you provided doesn’t quite make sense to me even in the above > scenario, because it shows accepting a string scalar as a type, but then > somehow that value is an object with methods, not a scalar? Is that a further > misunderstanding on my point, or is that example meant to include a > hypothetical `GuidLike` ‘union class’ which has `types Guid|string` in it?. > > Looking back at your original proposal’s examples, it becomes clearer with > the above understanding (if that’s what you meant), but it also seems even > _less_ intuitive to me now, even though I understand (well, I *think* I do) > what you’re actually suggesting, and I still don’t really see the benefits > you’re proposing. > > If a method accepts a parameter with a union type of `GUID|string`, yes you > have to do some work to verify that the string is in fact a guid - possibly > converting it into an instance of GUID along the way. > > If a method has a signature using a ‘Union Class type’ which has a `types > GUID|string` keyword, you _still_ have to verify that the result of value() > (or toString()) is in fact a valid GUID - because the calling site has just > given you a string, and the ‘magic’ has boxed that up into an object.. so you > can call a method to get back the same value, right? > > > > Cheers > > Stephen >