Mixed mode methods

  106829
September 2, 2019 14:03 cschneid@cschneid.com (Christian Schneider)
Hi internals,

While some people are trying to make PHP a stricter language I'm also interested in making it a more flexible language by allowing to opt-in to advanced features for people who want it.
Please don't shoot this down just because you are not the target audience of such a feature ;-)

Toying around with PHP 8 I noticed that it is now impossible to have methods which can be called both statically and in an object.
This proposal brings back mixed mode methods both for extensions and a new syntax for user-land functions to explicitly allow it.

Motivation:
- Make migration of tried-and-tested mixed mode methods easier while having to explicitly declare it. So yes, unchanged code gets the new stricter semantics but updating code to the new (and from now on well-defined) behaviour can be done by simply extending the function definition. Having to rewrite such code to two different method names can be tedious and error-prone without big benefit.
- Allowing mixed mode methods as a form of name-overload in cases having to have two separate names for static/non-static is a WTF.
- Reviving things like
	$elements = DOMDocument::loadXML($html)->childNodes;
   instead of having to rewrite them to something like
	($dom = new DOMDocument)->loadXML($html);
	$elements = $dom->childNodes;
   by adding ZEND_ACC_ALLOW_STATIC to the method signature (ext/dom/document.c currently still contains code to handle both cases).

A first shot at an implementation was done using the syntax ?static and using isset($this) to determine the current mode:
class A {
	var $dynamic = 'dynamic';
	?static function mixedmodefn() { return isset($this) ? $this->dynamic : 'static'; }
}
allowing both (new A)->mixedmodefn() and A::mixedmodefn().

The implementation (including a test) can be found at
	https://github.com/php/php-src/compare/master...chschneider:optional_static

If people are interested I could create an RFC for this.

Regards,
- Chris
  106830
September 2, 2019 14:13 nikita.ppv@gmail.com (Nikita Popov)
On Mon, Sep 2, 2019 at 4:03 PM Christian Schneider <cschneid@cschneid.com>
wrote:

> Hi internals, > > While some people are trying to make PHP a stricter language I'm also > interested in making it a more flexible language by allowing to opt-in to > advanced features for people who want it. > Please don't shoot this down just because you are not the target audience > of such a feature ;-) > > Toying around with PHP 8 I noticed that it is now impossible to have > methods which can be called both statically and in an object. > This proposal brings back mixed mode methods both for extensions and a new > syntax for user-land functions to explicitly allow it. > > Motivation: > - Make migration of tried-and-tested mixed mode methods easier while > having to explicitly declare it. So yes, unchanged code gets the new > stricter semantics but updating code to the new (and from now on > well-defined) behaviour can be done by simply extending the function > definition. Having to rewrite such code to two different method names can > be tedious and error-prone without big benefit. > - Allowing mixed mode methods as a form of name-overload in cases having > to have two separate names for static/non-static is a WTF. > - Reviving things like > $elements = DOMDocument::loadXML($html)->childNodes; > instead of having to rewrite them to something like > ($dom = new DOMDocument)->loadXML($html); > $elements = $dom->childNodes; > by adding ZEND_ACC_ALLOW_STATIC to the method signature > (ext/dom/document.c currently still contains code to handle both cases). > > A first shot at an implementation was done using the syntax ?static and > using isset($this) to determine the current mode: > class A { > var $dynamic = 'dynamic'; > ?static function mixedmodefn() { return isset($this) ? > $this->dynamic : 'static'; } > } > allowing both (new A)->mixedmodefn() and A::mixedmodefn(). > > The implementation (including a test) can be found at > > https://github.com/php/php-src/compare/master...chschneider:optional_static > > If people are interested I could create an RFC for this. >
I'm not in favor of this, but I'll also say that I don't hate it either. The big problem with what we had before was that any random instance method could also be used as a static method, which in 99% of the cases was not intended and would not work. Additionally, the engine had to always account for this edge-case possibility and perform extra checks that only rarely did something useful. Your proposal to use an explicit ?static annotation leaves this as a questionable coding pattern, but I don't see any big technical problems with it. Regards, Nikita
  106831
September 2, 2019 15:01 Danack@basereality.com (Dan Ackroyd)
On Mon, 2 Sep 2019 at 15:03, Christian Schneider <cschneid@cschneid.com> wrote:
> > Please don't shoot this down just because you are not the target audience of such a feature
That is always good advice. When you draft the RFC, I strongly recommend coming up with a line of reasoning of why creating static versions of functions like this: class Foo { static function createFromXml($html) { $instance = new static(); $instance>loadXml(); return $instance; } function loadXml($html) { //... } } Are such a terrible burden, that keeping the mixed mode calling is a desirable thing. I say that as I think it would need a strong justification rather than just "it's something that could be done". In particular, there are tools like https://github.com/rectorphp/rector that I understand could be used to do this automatically, at least for userland code. Also(, without checking to see if it's feasible,) to me a less surprising approach would be to allow static and instance methods to be declared separately with the same method name. class Foo { static function loadXml() { echo "I am static method\n"; } function loadXml() { echo "I am instance method\n"; } } Foo::loadXml(); (new Foo())->loadXml(); // output is // I am static method // I am instance method Although that doesn't meet your goal of allowing seamless upgrades, it seems like a better approach to allowing that sort of thing in general. At least in the sense of, if I had to explain this capability to a junior programmer, explaining that the static method is used when called statically, and the instance method is used when called on an instance, would be easier to explain that '$this' might or not be there. cheers Dan Ack
  106832
September 2, 2019 16:02 cschneid@cschneid.com (Christian Schneider)
Am 02.09.2019 um 17:01 schrieb Dan Ackroyd <danack@basereality.com>:
> Also(, without checking to see if it's feasible,) to me a less > surprising approach would be to allow static and instance methods to > be declared separately with the same method name. > > class Foo { > static function loadXml() { > echo "I am static method\n"; > } > > function loadXml() { > echo "I am instance method\n"; > } > }
I was considering this approach but that is a special case of Ad hoc polymorphism (https://en.wikipedia.org/wiki/Ad_hoc_polymorphism). And that's a path I don't want to go down, I don't think that's a good fit for PHP. It felt more natural to model it similarly to nullable return types. - Chris
  106835
September 2, 2019 18:03 andreas@dqxtech.net (Andreas Hennings)
In my experience, things that have different roles or behavior should
be differently named.

I have seen a lot of functions with dynamic return types based on a
mode set in the parameter (e.g. the return type could be string,
string[] or string[][] depending on whether some parameter is NULL or
not). In all cases, the confusion and ambiguity caused by this was far
greater than the supposed / perceived convenience.
Often enough, the increased complexity of control flow branches inside
the function would lead to bugs, which could remain undiscovered for
years.

Maybe the only benefits were to not pollute the global namespace as
much, and to give access to the same static variables for caching.
None of these benefits apply in an object-oriented scenario.

A method body which has to handle and distinguish static and
non-static calls would easily suffer from the same problems.

On Mon, 2 Sep 2019 at 18:03, Christian Schneider <cschneid@cschneid.com> wrote:
> > Am 02.09.2019 um 17:01 schrieb Dan Ackroyd <danack@basereality.com>: > > Also(, without checking to see if it's feasible,) to me a less > > surprising approach would be to allow static and instance methods to > > be declared separately with the same method name.
This would at least solve the problem of added complexity in the method body. Still, as Dan Ackroyd points out, there needs to be a clear benefit.
> I was considering this approach but that is a special case of Ad hoc polymorphism (https://en.wikipedia.org/wiki/Ad_hoc_polymorphism). > And that's a path I don't want to go down, I don't think that's a good fit for PHP. > It felt more natural to model it similarly to nullable return types.
The value of parameter-based polymorphism is for cases where the calling code wants to handle all parameter types in one call, whereas the callee has different implementations for different parameter types. This is not the case here: The calling code does always know whether a call should be static or not, so it can choose the applicable method name. On Mon, 2 Sep 2019 at 18:03, Christian Schneider <cschneid@cschneid.com> wrote:
> > Am 02.09.2019 um 17:01 schrieb Dan Ackroyd <danack@basereality.com>: > > Also(, without checking to see if it's feasible,) to me a less > > surprising approach would be to allow static and instance methods to > > be declared separately with the same method name. > > > > class Foo { > > static function loadXml() { > > echo "I am static method\n"; > > } > > > > function loadXml() { > > echo "I am instance method\n"; > > } > > } > > I was considering this approach but that is a special case of Ad hoc polymorphism (https://en.wikipedia.org/wiki/Ad_hoc_polymorphism). > And that's a path I don't want to go down, I don't think that's a good fit for PHP. > It felt more natural to model it similarly to nullable return types. > > - Chris > > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: http://www.php.net/unsub.php >
  106839
September 4, 2019 00:09 larry@garfieldtech.com ("Larry Garfield")
On Mon, Sep 2, 2019, at 1:03 PM, Andreas Hennings wrote:


> The value of parameter-based polymorphism is for cases where the > calling code wants to handle all parameter types in one call, whereas > the callee has different implementations for different parameter > types. > > This is not the case here: The calling code does always know whether a > call should be static or not, so it can choose the applicable method > name.
This is what I don't get. How can it not? If a method is static-safe, and you want to call it from both static and object contexts... Make it a static method and call it statically from both contexts. That's fine. If it's not safe to call from a static context (because it reads $this), you couldn't call it statically anyway. If the method has 2 different logic paths, depending on if it's a static or object context, then it should be two different methods. Folding them into one just pushes more conditionals down deeper into the code where the cyclomatic complexity impact is greater. That's not a win. --Larry Garfield