Re: [RFC] Partial Function Application for instance of non-static methods ("$this")

From: Date: Sat, 07 Feb 2026 16:51:28 +0000
Subject: Re: [RFC] Partial Function Application for instance of non-static methods ("$this")
References: 1 2 3 4  Groups: php.internals 
Request: Send a blank email to internals+get-130051@lists.php.net to get a copy of this message
Hey Tim, I think you jumped to a conclusion without exploring the space of possibilities fully: On 6.2.2026 16:12:05, Tim Düsterhus wrote:
With regard to your comment on the PR and the “Open Issue” listed in the RFC about the name for the $this parameter in the resulting Closure we have two options to offer: 1. ClassName::methodName(this: ?) with the parameter being called $__this. 2. ClassName::methodName($this: ?) with the parameter being called $this. Notably ClassName::methodName(this: ?) with the parameter being called $this is not an option, because it would result in inconsistent behavior: ClassName::methodName(this: ?) semantically relies on $this never being a valid parameter name, such that this: ? can unambiguously refer to the “implicit $this value” for a method call. However when the parameter in the generated Closure would be called $this, there is some ambiguity for cases like:
     $c = DateTime::format(this: ?, format: ?);
     $c2 = $c(this: ?, format: 'c');
Is this: ? in the definition of $c2 referring to the $this parameter of the generated Closure or is it an attempt to partially apply the Closure object for the Closure::__invoke() method that is referenced by $c? Similarly allowing this: $object with a concrete value is explicitly disallowed, because of possible ambiguity with regard to inheritance:
     class P { public function m() { echo "P"; } }
     class C extends P { public function m() { echo "C"; } }
     // is this calling P::m() or is this calling C::m()?
     P::m(this: new C());
This would however prevent calling a partially applied instance method with named arguments:
     $c = DateTime::format(this: ?, format: ?);
     // Disallowed, because this: must be partially applied.
     $c(this: new DateTime(), format: 'c');
If the syntax to define the PFA was using C::m($this: ?), $this in the resulting Closure would just work like any other parameter. 1. ClassName::methodName(this: ?) with the parameter being called $__this. 2. ClassName::methodName($this: ?) with the parameter being called $this.
Allowing P::m(this: $object) with a concrete value should behave identically to P::m(this: ?)($object), which in turn should behave identically to $object->m() if called outside of C (or its children), otherwise equivalently to `private function forwardM() { return P::m(); } $object->forwardM();` (i.e. a (grand)parent class method can always be called). This approach is consistent with how method calling works in child classes. More normatively for any class P and method m, P::m(this: ?) needs to store the class where the PFA closure is created as called_scope on the Closure when that class is instanceof P unless P::m is abstract, so that any call to the resulting closure is checked against that called_scope: If the $this object the Closure is ultimately called with is instanceof the called_scope of the Closure, the specific given method must be called (i.e. specifically P::m()), otherwise the object is merely checked for being instanceof P and the method m on the $this object is called directly. Defining it this way preserves LSP guarantees, with maximum flexibility - making it for example perfectly possible to call array_map(ParentClass::someValue(this: ?), $objectsArray) without being surprised that it subtly actually calls the child method someValue on objects which are instanceof the containing class. The neat benefit is that any $obj->method() call is now generalized (on the surface of the language semantics, obviously not implementation wise) as {get_class($obj)}::method(this: $obj), which makes the this-PFA a trivial extension of just having ? for the this parameter. It also obsoletes any concerns about how the this parameter ends up called in the resulting PFA - because it's just a "normal" parameter then, from the perspective of the caller. The only small caveat is Closure::__invoke(this: ?), which literally is just sort of an identity function and thus useless. To solve that, we should just decide to have an explicit this parameter on Closures take precendence. (Ultimately __invoke is more of an implementation detail of Closure, than anything else). Also of note is that having a proper implicit $this parameter on methods must not have a position (as in positional parameters). Otherwise conflicts arise with e.g. the implicit $this forwarding in parent::m() syntax in class scope. (obviously, once you create a PFA P::m(this: ?, ?) the first positional parameter becomes the $this parameter and the original first parameter is now the second one.) To summarize: Please introduce a first-class implicit $this parameter on any non-static method call, and have PFA work naturally with it, without doing contortions in language semantics / introducing a very PFA-specific syntax. Thanks, Bob P.s.: You should possibly add to future scope allowing object::someMethod(this: ?) or class::someMethod(this: ?) to allow proper duck typing without having to know the actual object behind. That's something I see people asking for, too.

« previous php.internals (#130051) next »