Re: [PHP-DEV] Union Class Types (was Union Type (singular) straw manproposal)

  106903
September 8, 2019 13:56 php-lists@koalephant.com (Stephen Reay)
> On 6 Sep 2019, at 17:21, Mike Schinkel <mike@newclarity.net> wrote: > > Hi Stephen, > > Thank you again for the reply. > >> and wasn’t really built for that purpose AFAIK, but in ‘weak’ mode (i.e. no strict_types=1) it would be invoked if the destination type specified a string. I was implying that this behaviour could be expanded both with other integer casting methods and to allow it to work in ’strict’ mode with an approach similar to how php works now, rather than suggesting that it works right now. > > I guess I was asking how you would see that working and not referring to existing implementation. > > After working through a variety of examples I can't find one where a smart compiler can deduce that a __toString() can map to a string type. But that only works for types where we have a magic class, right? We don't have __toInt(), __toFloat(), __toArray() (yet), __toObject(), etc. And there was a lot of dissent on the __toArray() RFC if I am not mistaken? > > Also, what about unions of class instances? I don't think it is realistic to propose we'd have a __to*() magic method for every classname that is in our application, e.g. __toFoo(), __toBar(), etc.? if not, how would we handle it the general case of converting to a specific type? > > What I was proposing would have provided a single consistent model to handle typing. It is probably not perfect and there is probably a better way. But magic methods don't seem to be flexible enough to provide a consistent method across all potential type. Unless I am missing something? > > >> It’s true you can’t cast to an object (besides stdclass) - but again, my point was not so much that the functionality already exist, more so that your proposal seems to ignore any history of how php handles this type of thing, > > I don't think it is fair to say the proposal ignored PHP's history. I considered PHP's history at length, but I did not find anything in the current history that seems to be to be able to handle the use-case elegantly. > >> and is - to my eyes - very foreign in its approach. > > I certainly can't argue with this. Your perceptions are indeed yours, and they are relevant. > > The questions I ask though are: > > 1. Can we find a way to address the concern that is not foreign to PHP developers? > 2. If we cannot find a way, is there a harm to introducing new concepts to PHP developers? > > I'm happy either way. I'm just interested in seeing the language moved forward. > > BTW, I guess I don't see it as foreign because of my experience with some other languages. > > >> Also, your proposal seems to only work for an object that effectively represents a single value (otherwise how does `setValue/getValue` work without a property name?) - I’m not sure how many examples there are of classes that just wrap literally a single value into something else? > > I think maybe I did not make the crux of the proposal clear. I was not proposing any given class should be handled with these mechanisms. I was proposing a mechanism to allow defining of bespoke classes for unions, and then other mechanisms to handle them for passing and receiving parameters, and assigning values to types properties. > > I am also not sure how the mechanisms I proposed would even be relevant to other classes. Can you think of examples I am missing? > >> With ArrayObject, Exception etc (I assume you mean, when a class extends one) the behaviour is well understood - while the internals of how it works may differ, it’s conceptually no different than extending another user land class. Concepts like late static binding, the parent class, etc -how does for example an overriden method call the ‘built-in’ method? Or are the built-in methods effectively ‘final’? > > I feel like I can say exactly the same thing about the proposed union classes with the exception that I would need to say "the behaviour will be well understood." The point is that PHP has some classes and interfaces with magic that us mere mortals in userland are not empowered to use. The goals of this proposal is to scope out just a tiny bit more of that magic. > > But everything else about how the classes work would work exactly the same as classes currently work, at least for any existing behavior. (Selected new behavior defined in the proposal would be exclusive to union classes, but that is orthogonal to your stated concern.) > > Per the proposal an overridden method would be no different than an overridden method in any other class, unless there are edge-cases I am missing? > > Methods could potentially be final if the community adopted this proposal and that was preferred, but I think it would be better to allow them to be overridden. > > >> If your goal is to allow a typed property/parameter to accept an object and “know” how to convert it (e.g. passing a hypothetical instance of a `Money` class to an int (or a float, if you’re feeling reckless)) I would see it as being closest in concept to how the JSONSerializable interface works: your class implements the interface, so which defines a method that must return the given type (e.g., forgiving the horrendous name an `Intable` interface might define `function toInt(): int`. When doing type checks, if it’s an object implementing that interface, the method is called, similarly to how when an object is used in a string context, __toString is called if it exists now (but adapted to work when strict_mode=1) > > Knowing how to convert is one thing, but probably the most fundamental aspect of the proposal is how a value valid type in the union is passed to a function or assigned to the property and the action of passing or assigning "boxes" into an object. And then provide a clean type-safe interface, all without requiring such a large amount of syntax because a large amount of syntax probably means few if any developers would ever actually use it. > > To whit: > function processStringGuid(string $guid) { > echo $guid; > } > function processObjectGuid(Guid $guid) { > echo $guid->value(); > } > function process(Guid|string $guid) { > switch ($guid->type()) { > case 'string': > processStringGuid( $guid->toString() ); > break; > case 'Guid': > processObjectGuid( $guid->toGuid() ); > break; > } > } > process('9fb7f05e-5c15-42a7-8338-e1b3bb9311cc'); > process(new Guid('9fb7f05e-5c15-42a7-8338-e1b3bb9311cc')); > If process() instead looks like the following, will static analyzers be able to (easily) verify that $guid is a Guid in the lower call to processObjectGuid()? > function process(Guid|string $guid) { > if ( is_string( $guid ) ) { > processStringGuid( $guid ); > } else { > processObjectGuid( $guid ); > } > } > Also, how obvious is it to readers of the code that $guid is a Guid in the lower call to processObjectGuid(), in the latter example, vs. the proposed solution? Especially if process() were 100 lines instead of five? (I know, write short functions. But there is a 1400 line function I inherited that I am working my way through refactoring, and trying not to break.) > >> _toString() only works (i.e. to pass an object to a string param/property) right now in lax-mode. I was suggesting that it (and matching __toInt/etc methods) could be expanded to be called in strict mode too > > But that only works with predefined types? Not classes that we can declare, right? > >> - but my personal preference would still be that an interface is ’nicer’. > > I'd be all for an interface, if I could figure out how to leverage them to achieve the same goals. > > Can you suggest how an interface-based approach would work instead? (And can we put magic methods as a solution to bed?) > > Anyway, thank you for a really engaging dialog. > > -Mike > > P.S. >> __toString is much older than type hints (and especially scalar type hints) in php > > Just to clarify, I am very familiar with the 5.x history of PHP. I've been been programming in PHP since 2006. I started right when 5.2 was released and __toString() was only called for echo and print. I say this only because the person who contacted me off the list somehow got the impression that I was not intimately familiar with userland PHP so maybe you were thinking the same? >
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
  106905
September 8, 2019 17:44 mike@newclarity.net (Mike Schinkel)
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-lists@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 >
  106906
September 8, 2019 18:19 php-lists@koalephant.com (Stephen Reay)
> On 9 Sep 2019, at 00:44, Mike Schinkel <mike@newclarity.net> wrote: > > 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-lists@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 >> >
Hi Mike, Thanks for the confirmation/clarification. So let me start out by clarifying that what *I* was suggesting with unions is quite a different concept than you’re talking about. I was talking about allowing for ‘automatic’, ‘casting’ (in a way that isn't possible now except for strings) of objects when past to into a context where a simple (scalar|array) type is dictated. The end result would be the type specified (irrespective of it being a union or not in fact), and yes, I’m aware there’s potential ambiguous cases - what if the destination accepts `string|int` and the object supports both interfaces? Ok - the example given makes “sense” with the understanding that it’s alternative ‘short’ syntax - but that makes it yet another step further from ‘intuitive’ to me, compared with current type hints, and the extension to allow unions Nikita introduced - and if Im not mistaken it makes it incompatible too (i.e. you can’t have the behaviour Nikita’s RFC describes and what you describe with the ‘anonymous unions’ - they’re too very different approaches using the same syntax. I think the intuitiveness factor is easier to explain if you consider the case of the Number example - plenty of things will work exactly as you expect if you use either an int or a float. But then with your proposal suddenly you don’t have a scalar variable any more - yes I know you can call `value()` - part of the point of type hints is to reduce boilerplate with type checking arguments within the function body - and now you have to call a method to get a usable value anyway? The GUID example still confuses me in terms of how your example ‘helps’. If $guid->getGuid returns null (signifying it’s a string?) presumably I still want to do something with that string - otherwise I wouldn’t have accepted it as a valid type - at which point I might as well just do a `instanceof` or `is_string` call - but now I have to check if the value I was given is null, even if I didn’t specify the parameter as being nullable? I think (or I hope so at least) we’re at least both clear what the other is talking about now. Enjoy your conference! Cheers Stephen
  106907
September 8, 2019 19:11 mike@newclarity.net (Mike Schinkel)
Hi Stephen,

Thanks again.

> So let me start out by clarifying that what *I* was suggesting with unions is quite a different concept than you’re talking about.
I did not realize you were proposing a different solution, too. Sorry I missed that.
> yes, I’m aware there’s potential ambiguous cases - what if the destination accepts `string|int` and the object supports both interfaces?
Precisely. And the goal of my proposal was in part to eliminate that ambiguity.
> Ok - the example given makes “sense” with the understanding that it’s alternative ‘short’ syntax - but that makes it yet another step further from ‘intuitive’ to me, compared with current type hints, and the extension to allow unions Nikita introduced -
Acknowledged.
> and if Im not mistaken it makes it incompatible too (i.e. you can’t have the behaviour Nikita’s RFC describes and what you describe with the ‘anonymous unions’ - they’re too very different approaches using the same syntax.
I do not see how they are incompatible except for the details mine changes, but maybe that is moot at this point?
> I think the intuitiveness factor is easier to explain if you consider the case of the Number example - plenty of things will work exactly as you expect if you use either an int or a float. But then with your proposal suddenly you don’t have a scalar variable any more - yes I know you can call `value()` - part of the point of type hints is to reduce boilerplate with type checking arguments within the function body - and now you have to call a method to get a usable value anyway?
I do not see any technical reason why mine would not be able to behave the same using the magic methods you were proposing such as __toString() and for other scalar types — since fundamentally the proposed union type would be a class anyway. When used that way it would nullify the benefit of the explicit typing, but if that is what the developer wanted who was using then why not? In that context it would be almost the same as just passing in a scalar.
> The GUID example still confuses me in terms of how your example ‘helps’. If $guid->getGuid returns null (signifying it’s a string?) presumably I still want to do something with that string - otherwise I wouldn’t have accepted it as a valid type - at which point I might as well just do a `instanceof` or `is_string` call - but now I have to check if the value I was given is null, even if I didn’t specify the parameter as being nullable?
It does not help that way. The main help is with $guid->type() which can be used in a switch. It's just cleaner code, though I can appreciate that some might see this as not really a benefit: function processGuid(string|Guid $guid) { switch( $guid->type()) { case 'Guid': $guid->toGuid()->doSomething(); break; case 'string': $g = new Guid($guid->toString()); $g-> doSomething(); break; } } The other benefit is that it becomes a first-class concern that can be extended when named union classes are used so I could implement this inside the union class and use it in many functions like this, if I wanted, rather than have to use type checking boilerplate in Every. Single. Function: class GuidUnion { types string|Guid public function doSomething() { switch( $this->type()) { case 'Guid': $this->toGuid()->doSomething(); break; case 'string': $g = new Guid($this->toString()); $g-> doSomething(); break; } } } /** * HERE is where you start seeing a real benefit... */ function processGuid1(GuidUnion $guid) { $guid->doSomething(); } function processGuid2(GuidUnion $guid) { $guid->doSomething(); } function processGuid3(GuidUnion $guid) { $guid->doSomething(); }
> I think (or I hope so at least) we’re at least both clear what the other is talking about now.
Hopefully, with that last point clarified, yes. Thanks again. -Mike
  106908
September 8, 2019 19:16 php-lists@koalephant.com (Stephen Reay)
> On 9 Sep 2019, at 02:11, Mike Schinkel <mike@newclarity.net> wrote: > > I did not realize you were proposing a different solution, too. Sorry I missed that. >
I didn’t mean in terms of an actual proposal, more so identifying what I think would be more intuitive (mostly based on what others have discussed/mentioned/proposed previously) in what I thought was a similar ‘problem space’ - but it turns out we’re talking about quite different things anyway!
  106930
September 11, 2019 13:07 larry@garfieldtech.com ("Larry Garfield")
On Sun, Sep 8, 2019, at 2:11 PM, Mike Schinkel wrote:
> Hi Stephen, > > Thanks again. > > > So let me start out by clarifying that what *I* was suggesting with unions is quite a different concept than you’re talking about. > > I did not realize you were proposing a different solution, too. Sorry I > missed that. > > > yes, I’m aware there’s potential ambiguous cases - what if the destination accepts `string|int` and the object supports both interfaces? > > > Precisely. And the goal of my proposal was in part to eliminate that ambiguity. > > > Ok - the example given makes “sense” with the understanding that it’s alternative ‘short’ syntax - but that makes it yet another step further from ‘intuitive’ to me, compared with current type hints, and the extension to allow unions Nikita introduced - > > Acknowledged. > > > and if Im not mistaken it makes it incompatible too (i.e. you can’t have the behaviour Nikita’s RFC describes and what you describe with the ‘anonymous unions’ - they’re too very different approaches using the same syntax. > > I do not see how they are incompatible except for the details mine > changes, but maybe that is moot at this point? > > > I think the intuitiveness factor is easier to explain if you consider the case of the Number example - plenty of things will work exactly as you expect if you use either an int or a float. But then with your proposal suddenly you don’t have a scalar variable any more - yes I know you can call `value()` - part of the point of type hints is to reduce boilerplate with type checking arguments within the function body - and now you have to call a method to get a usable value anyway? > > I do not see any technical reason why mine would not be able to behave > the same using the magic methods you were proposing such as > __toString() and for other scalar types — since fundamentally the > proposed union type would be a class anyway. When used that way it > would nullify the benefit of the explicit typing, but if that is what > the developer wanted who was using then why not? > > In that context it would be almost the same as just passing in a scalar. > > > The GUID example still confuses me in terms of how your example ‘helps’. If $guid->getGuid returns null (signifying it’s a string?) presumably I still want to do something with that string - otherwise I wouldn’t have accepted it as a valid type - at which point I might as well just do a `instanceof` or `is_string` call - but now I have to check if the value I was given is null, even if I didn’t specify the parameter as being nullable? > > It does not help that way. The main help is with $guid->type() which > can be used in a switch. It's just cleaner code, though I can > appreciate that some might see this as not really a benefit: > > function processGuid(string|Guid $guid) { > switch( $guid->type()) { > case 'Guid': > $guid->toGuid()->doSomething(); > break; > case 'string': > $g = new Guid($guid->toString()); > $g-> doSomething(); > break; > } > } > > The other benefit is that it becomes a first-class concern that can be > extended when named union classes are used so I could implement this > inside the union class and use it in many functions like this, if I > wanted, rather than have to use type checking boilerplate in Every. > Single. Function: > > class GuidUnion { > types string|Guid > public function doSomething() { > switch( $this->type()) { > case 'Guid': > $this->toGuid()->doSomething(); > break; > case 'string': > $g = new Guid($this->toString()); > $g-> doSomething(); > break; > } > } > } > > /** > * HERE is where you start seeing a real benefit... > */ > function processGuid1(GuidUnion $guid) { > $guid->doSomething(); > } > function processGuid2(GuidUnion $guid) { > $guid->doSomething(); > } > function processGuid3(GuidUnion $guid) { > $guid->doSomething(); > } > > > > I think (or I hope so at least) we’re at least both clear what the other is talking about now. > > Hopefully, with that last point clarified, yes. > > Thanks again. > > -Mike
It seems like what you're describing here is more user-customizable autoboxing than anything to do with union types. Viz, since a Guid has a natural and round-trippable string representation, you want to be able to ask for a Guid, be given a string, and then work with it as a Guid either way. For that, it seems like you'd want more of a fromString() method (magic or interface): class Guid implements FromStringInterface { public static function fromString(string $s) { if (is_valid_guid($s)) { return new static($s); } throw new \TypeError(); } } function needGuid(Guid $g) { $g->doGuidThings(); } needGuid('a9d05a5f-e643-441e-8784-cfd613072072'); Whether or not something like that is wise I don't know, but it seems like it would resolve the use case you describe better than fiddling with union types. Or am I totally misunderstanding you as well? :-) --Larry Garfield
  106984
September 12, 2019 17:12 mike@newclarity.net (Mike Schinkel)
Hi Larry,

Thank you for the comment.

> It seems like what you're describing here is more user-customizable autoboxing than anything to do with union types.
Your first statement ("user-customizable autoboxing") is probably fair to say. OTOH, rather than say "anything to do with union type" I would instead say "where union-types is an excellent use-case for auto-boxing."
> For that, it seems like you'd want more of a fromString() method (magic or interface):
That would certainly work. But unfortunately that requires the class developer to be forward-thinking enough to implement methods required for all the use-cases of their class users, and I think it is unlikely we can depend on that case for most classes in the wild. What I'd rather see is a solution that gives the user of the class more control and a well-known/consistent programming model (e.g. ->toGuid().)
> Whether or not something like that is wise I don't know, but it seems like it would resolve the use case you describe better than fiddling with union types. Or am I totally misunderstanding you as well? :-)
I made the proposal because I felt that auto-boxing was the best solutions for the union-types use-case. So if renaming the proposal to "user-customizable autoboxing" is more preferred by others I'd see that to be fine, assuming it was implemented in a manner that allowed for adding the use-case of union types. :-) -Mike P.S. I am not seeing much traction from this proposal so even though we discuss the nuances it seems likely it won't matter beyond this discussion.
> On Sep 11, 2019, at 6:07 AM, Larry Garfield <larry@garfieldtech.com> wrote: > > On Sun, Sep 8, 2019, at 2:11 PM, Mike Schinkel wrote: >> Hi Stephen, >> >> Thanks again. >> >>> So let me start out by clarifying that what *I* was suggesting with unions is quite a different concept than you’re talking about. >> >> I did not realize you were proposing a different solution, too. Sorry I >> missed that. >> >>> yes, I’m aware there’s potential ambiguous cases - what if the destination accepts `string|int` and the object supports both interfaces? >> >> >> Precisely. And the goal of my proposal was in part to eliminate that ambiguity. >> >>> Ok - the example given makes “sense” with the understanding that it’s alternative ‘short’ syntax - but that makes it yet another step further from ‘intuitive’ to me, compared with current type hints, and the extension to allow unions Nikita introduced - >> >> Acknowledged. >> >>> and if Im not mistaken it makes it incompatible too (i.e. you can’t have the behaviour Nikita’s RFC describes and what you describe with the ‘anonymous unions’ - they’re too very different approaches using the same syntax. >> >> I do not see how they are incompatible except for the details mine >> changes, but maybe that is moot at this point? >> >>> I think the intuitiveness factor is easier to explain if you consider the case of the Number example - plenty of things will work exactly as you expect if you use either an int or a float. But then with your proposal suddenly you don’t have a scalar variable any more - yes I know you can call `value()` - part of the point of type hints is to reduce boilerplate with type checking arguments within the function body - and now you have to call a method to get a usable value anyway? >> >> I do not see any technical reason why mine would not be able to behave >> the same using the magic methods you were proposing such as >> __toString() and for other scalar types — since fundamentally the >> proposed union type would be a class anyway. When used that way it >> would nullify the benefit of the explicit typing, but if that is what >> the developer wanted who was using then why not? >> >> In that context it would be almost the same as just passing in a scalar. >> >>> The GUID example still confuses me in terms of how your example ‘helps’. If $guid->getGuid returns null (signifying it’s a string?) presumably I still want to do something with that string - otherwise I wouldn’t have accepted it as a valid type - at which point I might as well just do a `instanceof` or `is_string` call - but now I have to check if the value I was given is null, even if I didn’t specify the parameter as being nullable? >> >> It does not help that way. The main help is with $guid->type() which >> can be used in a switch. It's just cleaner code, though I can >> appreciate that some might see this as not really a benefit: >> >> function processGuid(string|Guid $guid) { >> switch( $guid->type()) { >> case 'Guid': >> $guid->toGuid()->doSomething(); >> break; >> case 'string': >> $g = new Guid($guid->toString()); >> $g-> doSomething(); >> break; >> } >> } >> >> The other benefit is that it becomes a first-class concern that can be >> extended when named union classes are used so I could implement this >> inside the union class and use it in many functions like this, if I >> wanted, rather than have to use type checking boilerplate in Every. >> Single. Function: >> >> class GuidUnion { >> types string|Guid >> public function doSomething() { >> switch( $this->type()) { >> case 'Guid': >> $this->toGuid()->doSomething(); >> break; >> case 'string': >> $g = new Guid($this->toString()); >> $g-> doSomething(); >> break; >> } >> } >> } >> >> /** >> * HERE is where you start seeing a real benefit... >> */ >> function processGuid1(GuidUnion $guid) { >> $guid->doSomething(); >> } >> function processGuid2(GuidUnion $guid) { >> $guid->doSomething(); >> } >> function processGuid3(GuidUnion $guid) { >> $guid->doSomething(); >> } >> >> >>> I think (or I hope so at least) we’re at least both clear what the other is talking about now. >> >> Hopefully, with that last point clarified, yes. >> >> Thanks again. >> >> -Mike > > It seems like what you're describing here is more user-customizable autoboxing than anything to do with union types. Viz, since a Guid has a natural and round-trippable string representation, you want to be able to ask for a Guid, be given a string, and then work with it as a Guid either way. > > For that, it seems like you'd want more of a fromString() method (magic or interface): > > class Guid implements FromStringInterface { > > public static function fromString(string $s) { > if (is_valid_guid($s)) { > return new static($s); > } > throw new \TypeError(); > } > } > > function needGuid(Guid $g) { > $g->doGuidThings(); > } > > > needGuid('a9d05a5f-e643-441e-8784-cfd613072072'); > > Whether or not something like that is wise I don't know, but it seems like it would resolve the use case you describe better than fiddling with union types. Or am I totally misunderstanding you as well? :-) > > --Larry Garfield > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: http://www.php.net/unsub.php >