Re: Union Class Types (was Union Type (singular) str aw man proposal)

From: 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

« previous php.internals (#106930) next »