Re: [RFC] Partial Function Application for instance of non-static methods ("$this")
| From: | Mathieu Rochette | Date: | Fri, 06 Feb 2026 23:21:50 +0000 |
| Subject: | Re: [RFC] Partial Function Application for instance of non-static methods ("$this") | ||
| References: | 1 2 3 4 5 6 7 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-130041@lists.php.net to get a copy of this message | ||
On Friday, February 6th, 2026 at 21:32, Larry Garfield <larry@garfieldtech..com> wrote:
> On Fri, Feb 6, 2026, at 10:43 AM, Tim Düsterhus wrote:
> > Hi
> >
> > On 1/22/26 17:41, Larry Garfield wrote:
> >> More spitballing on my previous reply:
> >>
> >> class Test {
> >> public function stuff(int $a) {}
> >> }
> >>
> >> (Test)?->stuff(?);
> >
> > As mentioned in the sibling mail, this is existing syntax and thus
> > doesn't work.
>
> Sure, but we can fiddle with the details to find something that works. I also suggested
> something like these to Arnaud off-list:
>
> (?: Test)->stuff(?);
> (Test ?)->stuff(?);
> ((Test)?)->stuff(?);
>
>
> > Keeping full type information is the main benefit of PFA over “just
> > write a Closure”. Being able to reorder parameters as part of partial
> > application is another explicit feature that would not be supported by
> > that syntax.
>
> Honestly, I don't much care about the reordering. And with this approach you'd still
> be able to reorder the method params, just keep the object as first arg.
>
> >> That would then be a lot easier to write in cases where you're just dropping a
> >> $this->stuff() call into a pipe chain but want to receive $this.
> >
> > The use case for partially applying
Attachment: [application/pgp-keys] publickey - mathieu@rochette.cc - 0x8D30BA0E.asc
Attachment: [application/pgp-signature] OpenPGP digital signature signature.asc
$this is in cases
> > where you need a
> > “function handle”, that's why the examples are ones where the resulting
> > Closure is passed as a parameter to another function.
> >
> > Within a pipe chain you would just use the regular ->
> > operator on the
> > result of the previous step:
> >
> > $result = (trim($username)
> > |> $repository->findBy(name: ?)
> > )->getId();
> >
> > It would also naturally support ?-> in case your
> > repository returns
> > null when the user cannot be found.
>
> Directly in a pipe chain, sure. However, I would see this as most useful as a callback to
> array_map et al (or future equivalents).
>
> Eg:
>
> import_stuff()
> |> array_filter(?, (?)->hasField('comment')
> |> array_map((?)->save(), $records)
> ;
>
> In this case, the only thing being partialed is the object on which to invoke. So there's
> no need to think about reordering the params in the first place: There's just one.
>
> Needing to specify the type in this case:
>
> import_stuff()
> |> array_filter(?, Record::hasField('comment', ...)
> |> array_map(Record::save(...), $records)
> ;
>
> Just adds more noise, and IMO creates confusion between static and non-static methods.
>
> --Larry Garfield
>
I find the (Test ?)->method() suggestion quite nice. For 2 reasons, it looks similar
to a function argument ("Type $arg"), and following that, given that the type is optional
for function arguments, it could also be optional here to have (?)->method() or even
?->method() as a shortcut.
I understand it means some optimization cannot be done, but in a lot of case it doesn't matter,
and if I have to write DateTimeImmutable::format($this: ?, 'c') vs fn
($dt) => $dt->format('c'). The only benefit would be the optimization. so if
I don't care about that I'm still going to go for the shorter one unless this a
performance critical situation
Having (DateTimeImmutable ?)->format('c') and
?->format('c') as a non-optimized shortcut seems more interesting to me.
I'm not too bothered by the reordering, I think this still covers 99% of the use cases
---
That said, regarding your 2 suggestions, Tim, and after reading your explanations, my preference
goes for $this: ?
And yet more suggestions: DateTimeImmutable::methodName($: ?, ...)
and whatever is between $ and : could be used as the arg name of the
generated function (with something like "__this" being the default if omitted)
the $ prefix could also be replaced by any symbol not authorized at the start of an
argument name, eg:
* DateTimeImmutable::methodName(*it: ?, ...)
* DateTimeImmutable::methodName(~it: ?, ...)
* DateTimeImmutable::methodName(.it: ?, ...) (it's getting worse and worse,
ain't it?)
or, even more drastic changes:
* DateTimeImmutable::methodName(*it, ...)
* DateTimeImmutable::methodName(?this, ...)
(feel free to silently ignore them; I'm not putting that much thought into those suggestions)
regards,
Mathieu Rochette
Attachment: [application/pgp-keys] publickey - mathieu@rochette.cc - 0x8D30BA0E.asc
Attachment: [application/pgp-signature] OpenPGP digital signature signature.asc