> On 6 Sep 2019, at 17:21, Mike Schinkel <m...@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 -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php