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 `$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