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

From: Date: Sun, 08 Sep 2019 13:56:51 +0000
Subject: Re: Union Class Types (was Union Type (singular) straw man proposal)
References: 1 2 3 4 5 6 7  Groups: php.internals 
Request: Send a blank email to internals+get-106903@lists.php.net to get a copy of this message
> 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

« previous php.internals (#106903) next »