Re: Union Class Types (was Union Type (singular) str aw man proposal)
| From: | Larry Garfield | Date: | Wed, 11 Sep 2019 13:07:35 +0000 |
| Subject: | Re: Union Class Types (was Union Type (singular) str aw man proposal) | ||
| References: | 1 2 3 4 5 6 7 8 9 10 11 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-106930@lists.php.net to get a copy of this message | ||
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