> 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

Reply via email to