[RFC] Partial Function Application v2

php.internals

Larry Garfield

1 year ago
Hi folks. Arnaud and I would like to present take-2 at Partial Function Application. https://wiki.php.net/rfc/partial_function_application_v2 It is largely similar to the previous PFA proposal from 2021, though there are a number of changes. Most notably: * The implementation is simpler, because FCC already did part of the work. This RFC can build on it. * Constructors are not supported. * But optional arguments and named placeholders are supported. * It includes pipe-based optimizations. Note: We realize that this is a non-trivial RFC coming late in the cycle. We are proposing it now because, well, it's ready now. If the discussion goes smoothly, we're OK calling a vote on it for 8.5, especially as it would complement pipes so well. If the discussion runs longer, we're also OK with targeting 8.6 instead. We'll see how that goes. <floor opens for discussion, Larry falls through the trap door>
-- Larry Garfield larry@garfieldtech.com

Stephen Reay

1 year ago
> On 28 Jun 2025, at 12:15, Larry Garfield <larry@garfieldtech.com> wrote: > > Hi folks. Arnaud and I would like to present take-2 at Partial Function Application. > > https://wiki.php.net/rfc/partial_function_application_v2 > > It is largely similar to the previous PFA proposal from 2021, though there are a number of changes. Most notably: > > * The implementation is simpler, because FCC already did part of the work. This RFC can build on it. > * Constructors are not supported. > * But optional arguments and named placeholders are supported. > * It includes pipe-based optimizations. > > Note: We realize that this is a non-trivial RFC coming late in the cycle. We are proposing it now because, well, it's ready now. If the discussion goes smoothly, we're OK calling a vote on it for 8.5, especially as it would complement pipes so well. If the discussion runs longer, we're also OK with targeting 8.6 instead. We'll see how that goes. > > <floor opens for discussion, Larry falls through the trap door> > > -- > Larry Garfield > larry@garfieldtech.com >
Hi Larry, I noticed your list of places this applies says "all function/method calls" but then doesn't list object invocation (__invoke magic method). I assume that's just an oversight in the list of places it's applicable rather than an omission in support? Cheers Stephen

[ ]

1 year ago
On Sat, Jun 28, 2025, 8:07 AM Larry Garfield <larry@garfieldtech.com> wrote:
> Hi folks. Arnaud and I would like to present take-2 at Partial Function > Application. > > https://wiki.php.net/rfc/partial_function_application_v2 > > It is largely similar to the previous PFA proposal from 2021, though there > are a number of changes. Most notably: > > * The implementation is simpler, because FCC already did part of the > work. This RFC can build on it. > * Constructors are not supported. > * But optional arguments and named placeholders are supported. > * It includes pipe-based optimizations. > > Note: We realize that this is a non-trivial RFC coming late in the cycle. > We are proposing it now because, well, it's ready now. If the discussion > goes smoothly, we're OK calling a vote on it for 8.5, especially as it > would complement pipes so well. If the discussion runs longer, we're also > OK with targeting 8.6 instead. We'll see how that goes. >
Yea, thank you Arnaud and Larry! This is a great feature! Hope it will pass smoothly 🙂

Levi Morrison

1 year ago
On Fri, Jun 27, 2025 at 11:09 PM Larry Garfield <larry@garfieldtech.com> wrote:
> > https://wiki.php.net/rfc/partial_function_application_v2 > > * It includes pipe-based optimizations.
Good. I think this has a good chance at passing now. This small detail was one that was arduously discussed on and off list 5 years ago when I co-proposed it. For a dynamic language, I was a bit surprised by this. Since it can now be optimized away in one of its most common cases, that's a big deal, and will make certain people happy. Thanks for including this optimization.

Ben Ramsey

1 year ago
> On Jun 28, 2025, at 00:06, Larry Garfield <larry@garfieldtech.com> wrote: > > Hi folks. Arnaud and I would like to present take-2 at Partial Function Application. > > https://wiki.php.net/rfc/partial_function_application_v2 > > It is largely similar to the previous PFA proposal from 2021, though there are a number of changes. Most notably: > > * The implementation is simpler, because FCC already did part of the work. This RFC can build on it. > * Constructors are not supported. > * But optional arguments and named placeholders are supported. > * It includes pipe-based optimizations. > > Note: We realize that this is a non-trivial RFC coming late in the cycle. We are proposing it now because, well, it's ready now. If the discussion goes smoothly, we're OK calling a vote on it for 8.5, especially as it would complement pipes so well. If the discussion runs longer, we're also OK with targeting 8.6 instead. We'll see how that goes. > > <floor opens for discussion, Larry falls through the trap door> > > -- > Larry Garfield > larry@garfieldtech.com
Great RFC. It has my vote! Under the section “func_get_args() and friends,” one of the examples shows: $f = foo(?, ?, ??); I assume this is a typo? Cheers, Ben

Larry Garfield

1 year ago
On Sat, Jun 28, 2025, at 4:46 AM, Stephen Reay wrote:
> Hi Larry, > > I noticed your list of places this applies says "all function/method > calls" but then doesn't list object invocation (__invoke magic > method). I assume that's just an oversight in the list of places it's > applicable rather than an omission in support? > > Cheers > > Stephen
Yep, that's just an oversight. That should work, although it looks like we don't have a test for it yet. I have left a note for Arnaud to add one and updated the RFC. On Sun, Jun 29, 2025, at 7:29 PM, Ben Ramsey wrote:
> Under the section “func_get_args() and friends,” one of the examples shows: > > $f = foo(?, ?, ??); > > I assume this is a typo?
One too many ?. :-) Fixed thanks. --Larry Garfield

Tim Düsterhus

1 year ago
Hi Am 2025-06-28 07:06, schrieb Larry Garfield:
> https://wiki.php.net/rfc/partial_function_application_v2
Some thoughts, I did not yet take an in-depth look: - Will PFA be available in constant-expressions, following the “First Class Callables in constant expressions” RFC (https://wiki.php.net/rfc/fcc_in_const_expr)? - It would be good to include an example section not using a regular “free-standing” function in the RFC. The RFC already mentions that all callables are supported, but explicitly showing an example for the possible alternative syntaxes (including `$foo->bar(?)` and `$foo(?)` would be useful. - Starting with the “RFC Impact” section, several sections of the template are not filled in. Best regards Tim Düsterhus

Larry Garfield

1 year ago
On Mon, Jun 30, 2025, at 2:28 AM, Tim Düsterhus wrote:
> Hi > > Am 2025-06-28 07:06, schrieb Larry Garfield: >> https://wiki.php.net/rfc/partial_function_application_v2 > > Some thoughts, I did not yet take an in-depth look: > > - Will PFA be available in constant-expressions, following the “First > Class Callables in constant expressions” RFC > (https://wiki.php.net/rfc/fcc_in_const_expr)?
We hadn't discussed it, actually. I'll have to ask Arnaud how difficult that would be. If it's easy enough, I'd say yes it should, but if it proves complicated we may need to punt on it. I'll update the RFC once we know.
> - It would be good to include an example section not using a regular > “free-standing” function in the RFC. The RFC already mentions that all > callables are supported, but explicitly showing an example for the > possible alternative syntaxes (including `$foo->bar(?)` and `$foo(?)` > would be useful.
I have added more examples that cover non-function things.
> - Starting with the “RFC Impact” section, several sections of the > template are not filled in.
Trimmed, as there's nothing to say there AFAIK. --Larry Garfield

Tim Düsterhus

1 year ago
Hi Am 2025-06-28 07:06, schrieb Larry Garfield:
> Hi folks. Arnaud and I would like to present take-2 at Partial > Function Application. > > https://wiki.php.net/rfc/partial_function_application_v2
I've now had a *quick* look at the implementation and the following questions came up that the RFC does not answer (and the tests in the PR do not obviously answer either): How will PFA calls appear in a stack trace and how will PFA Closures look like to `var_dump()`, Reflection, and to observers? Classic FCC are 100% identical to the underlying function and thus can just “pretend” they are the underlying function, but that doesn't work for PFA. Consider the following: function foo(string $s, int $i) { var_dump($s, $i); } $f = foo("abc", ?); $f([]); How will the error message for the resulting TypeError look like? var_dump($f); // same $f How will the output look like? var_dump((new ReflectionFunction($f))->getName()); var_dump((new ReflectionFunction($f))->getParameters()); Ditto is_callable($f, callable_name: $name); var_dump($name); Ditto function foo(string $s, #[\SensitiveParameter] int $i) { throw new \Exception(); } $f = foo("abc", ?); $f(123); How will the stack trace look like? Does `#[\SensitiveParameter]` work properly? Best regards Tim Düsterhus

Arnaud.lb

1 year ago
Hi Tim, We will update the RFC, but here are a few answers: On Wednesday, July 2nd, 2025 at 17:05, Tim Düsterhus <tim@bastelstu.be> wrote:
> I've now had a quick look at the implementation and the following > questions came up that the RFC does not answer (and the tests in the PR > do not obviously answer either): > > How will PFA calls appear in a stack trace and how will PFA Closures > look like to `var_dump()`, Reflection, and to observers?
PFAs are instances of the Closure class (like FCCs), and will look like a Closure to `var_dump()`, Reflection, and observers. The Closure signature reflects the parameters that are accepted by the PFA, not the underlying function (so it exposes only unbound parameters). function f(int $a, int $b) { } $f = f(?, 2); echo new ReflectionFunction($f); // Output: Partial [ <user> function f ] { @@ test.php 5 - 5 - Parameters [1] { Parameter #0 [ <required> int $a ] } } PFA Reflection is tested in Zend/tests/partial_application/reflection_*.phpt. Parameter names, and which parameters are required, are defined by the RFC. Currently, a few things are broken in the implementation, including parameter default value reflection. Additionally, `var_dump()` exposes bound and unbound args (with the value of bound args). Currently the `var_dump()` output looks like this: object(Closure)#1 (5) { ["name"]=> string(1) "f" ["file"]=> string(%d) "test.php" ["line"]=> int(7) ["parameter"]=> array(1) { ["$a"]=> string(10) "<required>" } ["args"]=> array(2) { ["a"]=> NULL ["b"]=> int(2) } } PFAs do not appear in stack traces, only the function does.
> Classic FCC are 100% identical to the underlying function and thus can > just “pretend” they are the underlying function, but that doesn't work > for PFA. Consider the following: > > function foo(string $s, int $i) { > var_dump($s, $i); > } > > $f = foo("abc", ?); > > $f([]); > > How will the error message for the resulting TypeError look like?
Error messages refer to the underlying function as if it was called directly: Uncaught TypeError: foo(): Argument #2 ($i) must be of type int, array given, in test.php on line 7 The line number refers to the call site of the PFA ($f([])), not its instantiation. However, since PFAs must check argument count before binding them, errors related to argument count refer to the PFA itself. Currently the error message for $f() looks like this: Uncaught Error: not enough arguments for application of foo, 0 given and exactly 1 expected, declared in test.php on line 5 in test.php on line 7
> var_dump((new ReflectionFunction($f))->getName());
The underlying function name (like FCCs)
> var_dump((new ReflectionFunction($f))->getParameters());
See above
> is_callable($f, callable_name: $name); > var_dump($name);
Closure::__invoke (like FCCs)
> function foo(string $s, #[\SensitiveParameter] int $i) { > throw new \Exception(); > } > > $f = foo("abc", ?); > > $f(123); > > How will the stack trace look like? Does `#[\\SensitiveParameter]` work > properly?
This is broken, but the intent is to support attributes, so that SensitiveParameter and other attributes work as expected. Best Regards, Arnaud

Gina P. Banyard

1 year ago
On Wednesday, 2 July 2025 at 17:26, Arnaud Le Blanc <arnaud.lb@gmail.com> wrote:
> On Wednesday, July 2nd, 2025 at 17:05, Tim Düsterhus tim@bastelstu.be wrote: > > is_callable($f, callable_name: $name); > > var_dump($name); > > > Closure::__invoke (like FCCs)
I'm slightly hijacking this to mention that this seems like a bug in how an FCC currently grabs the name of a Closure, and have submitted https://github.com/php/php-src/pull/19011 as a fix. This came up during my review of Daniel's implementation for the deprecation of returning non string values from output handlers that was accepted for 8.4 but didn't make it. As such, could you pull in this patch and see how it affects error messages? Otherwise, having had a glimpse of the implementation, I can't really see any reason for it to not be voted on for acceptance in 8.5. Best regards, Gina P. Banyard

Tim Düsterhus

1 year ago
Hi Am 2025-07-02 18:23, schrieb Arnaud Le Blanc:
> We will update the RFC, but here are a few answers:
I don't think this has happened yet.
> On Wednesday, July 2nd, 2025 at 17:05, Tim Düsterhus <tim@bastelstu.be> > wrote: >> How will PFA calls appear in a stack trace and how will PFA Closures >> look like to `var_dump()`, Reflection, and to observers? > > PFAs are instances of the Closure class (like FCCs), and will look > like a Closure to `var_dump()`, Reflection, and observers.
Yes, that was clear.
> The Closure signature reflects the parameters that are accepted by the > PFA, not the underlying function (so it exposes only unbound > parameters).
That makes sense.
> Additionally, `var_dump()` exposes bound and unbound args (with the > value of bound args). Currently the `var_dump()` output looks like > this: > > object(Closure)#1 (5) { > ["name"]=> > string(1) "f" > ["file"]=> > string(%d) "test.php" > ["line"]=> > int(7) > ["parameter"]=> > array(1) { > ["$a"]=> > string(10) "<required>" > } > ["args"]=> > array(2) { > ["a"]=> > NULL > ["b"]=> > int(2) > } > }
Thank you. I'm not sure if I like this, particularly the `name`. Compared to FCCs saying that a PFA of `f` has the name `f` is misleading, since the parameter list is different and thus functions are not interchangeable. Instead the name could perhaps be `{partial:f()}`, similarly to the new closure names?
> PFAs do not appear in stack traces, only the function does.
This would be consistent with `__call()`, but similarly to the above, it could be misleading, since the parameters shown in the stack trace do not match what the user has written at the call site. Would it be possible to insert a fake frame for the call to the partial or is this prohibitively expensive?
> Error messages refer to the underlying function as if it was called > directly: > > Uncaught TypeError: foo(): Argument #2 ($i) must be of type int, > array given, in test.php on line 7 > > The line number refers to the call site of the PFA ($f([])), not its > instantiation.
See above regarding the stack trace.
> However, since PFAs must check argument count before binding them, > errors related to argument count refer to the PFA itself. Currently > the error message for $f() looks like this: > > Uncaught Error: not enough arguments for application of foo, 0 > given and exactly 1 expected, declared in test.php on line 5 in > test.php on line 7
Yes, that makes sense. It's probably not necessary to indicate where the original function was declared, we don't do this for other errors related to the signature either.
>> var_dump((new ReflectionFunction($f))->getName()); > > The underlying function name (like FCCs)
See above for my `var_dump()` comments.
>> is_callable($f, callable_name: $name); >> var_dump($name); > > Closure::__invoke (like FCCs)
I changed that with PHP 8.5. With PHP 8.5 `is_callable()` is consistent with ReflectionFunction::getName() for FCCs. See: https://github.com/php/php-src/pull/18063 Best regards Tim Düsterhus

Rob Landers

1 year ago
On Sat, Jun 28, 2025, at 07:06, Larry Garfield wrote:
> Hi folks. Arnaud and I would like to present take-2 at Partial Function Application. > > https://wiki.php.net/rfc/partial_function_application_v2 > > It is largely similar to the previous PFA proposal from 2021, though there are a number of changes. Most notably: > > * The implementation is simpler, because FCC already did part of the work. This RFC can build on it. > * Constructors are not supported. > * But optional arguments and named placeholders are supported. > * It includes pipe-based optimizations. > > Note: We realize that this is a non-trivial RFC coming late in the cycle. We are proposing it now because, well, it's ready now. If the discussion goes smoothly, we're OK calling a vote on it for 8.5, especially as it would complement pipes so well. If the discussion runs longer, we're also OK with targeting 8.6 instead. We'll see how that goes. > > <floor opens for discussion, Larry falls through the trap door> > > -- > Larry Garfield > larry@garfieldtech.com >
Hi Larry, I hope your trip through the trap door is largely uneventful with a smooth integration into 8.5. My only question: why does this implementation care if you specify too many arguments when PHP doesn’t care if you call a function with too many arguments? I think it’s a good thing that it cares, and I think PHP itself should care, but should this RFC change that expectation? — Rob

Larry Garfield

1 year ago
On Fri, Jul 4, 2025, at 5:27 PM, Rob Landers wrote:
> On Sat, Jun 28, 2025, at 07:06, Larry Garfield wrote: >> Hi folks. Arnaud and I would like to present take-2 at Partial Function Application. >> >> https://wiki.php.net/rfc/partial_function_application_v2 >> >> It is largely similar to the previous PFA proposal from 2021, though there are a number of changes. Most notably: >> >> * The implementation is simpler, because FCC already did part of the work. This RFC can build on it. >> * Constructors are not supported. >> * But optional arguments and named placeholders are supported. >> * It includes pipe-based optimizations. >> >> Note: We realize that this is a non-trivial RFC coming late in the cycle. We are proposing it now because, well, it's ready now. If the discussion goes smoothly, we're OK calling a vote on it for 8.5, especially as it would complement pipes so well. If the discussion runs longer, we're also OK with targeting 8.6 instead. We'll see how that goes. >> >> <floor opens for discussion, Larry falls through the trap door> >> >> -- >> Larry Garfield >> larry@garfieldtech.com >> > > Hi Larry, > > I hope your trip through the trap door is largely uneventful with a > smooth integration into 8.5. > > My only question: why does this implementation care if you specify too > many arguments when PHP doesn’t care if you call a function with too > many arguments? > > I think it’s a good thing that it cares, and I think PHP itself should > care, but should this RFC change that expectation? > > — Rob
Largely because it conflicts with the intent of the closure author, and may have unexpected interaction with optional args otherwise. Eg: function f($a, $b = 0) {} $f = f(?); $f(1, 2); Is 2 bound to $b ? If yes this goes against the intent of the PFA creator. But if not this is weird. Therefore it’s better if that’s not allowed, as that's the least-weird outcome. If the closure author wants to allow trailing arguments, they can use the ... placeholder, which would allow for that. So: function f($a, $b = 0) {} $f = f(?, ...); $f(1, 2); It's self-evident that trailing args are allowed, and that it's the author's intent, so in this case all is well and there's no (unexpected) weirdness. --Larry Garfield

Levi Morrison

1 year ago
> My only question: why does this implementation care if you specify too many arguments when PHP doesn’t care if you call a function with too many arguments?
That is only true for userland functions, by the way. Internal functions do care. Historically, we could not do anything about this because variadic functions were only introduced in 5.6, so many functions did not specify that they were variadic and just used func_get_args(). Additionally, some people think it should be unified the other way--internal functions should accept extra args. Anyway, there you go on some history and reasoning of why it is the way it is.

Larry Garfield

1 year ago
On Sat, Jun 28, 2025, at 12:06 AM, Larry Garfield wrote:
> Hi folks. Arnaud and I would like to present take-2 at Partial > Function Application. > > https://wiki.php.net/rfc/partial_function_application_v2 > > It is largely similar to the previous PFA proposal from 2021, though > there are a number of changes. Most notably: > > * The implementation is simpler, because FCC already did part of the > work. This RFC can build on it. > * Constructors are not supported. > * But optional arguments and named placeholders are supported. > * It includes pipe-based optimizations. > > Note: We realize that this is a non-trivial RFC coming late in the > cycle. We are proposing it now because, well, it's ready now. If the > discussion goes smoothly, we're OK calling a vote on it for 8.5, > especially as it would complement pipes so well. If the discussion > runs longer, we're also OK with targeting 8.6 instead. We'll see how > that goes. > > <floor opens for discussion, Larry falls through the trap door>
Hi folks. Just a quick update: We've made one small change to the RFC. Specifically, in order to prevent accidentally calling optional arguments from callback locations like array_map() or array_find(), a partial created with foo(?) will ignore any additional arguments passed to it, and will not pass those through to the underlying function. A partial that uses foo(?, ...) will pass through whatever it gets. This is mainly to avoid passing an array key from those functions to a callback function that has an optional second parameter, which is not intended to get a key string. In practice this is what most people would expect would happen, but we're calling it out explicitly. (I'm not even sure it's a behavior change from what we had before, in practice.) cf: https://wiki.php.net/rfc/partial_function_application_v2#extraneous_arguments It seems the discussion has quieted down and wasn't particularly contentious to begin with (whew), so we're just about ready for a vote. However, Arnaud went on vacation and didn't remember to tell me when he'd be back. :-) So I'm going to wait a few more days just in case he has any last minute comments, but start the vote either when he returns or Monday the 28th, whichever comes first. (That gets the vote complete before the deadline for 8.5.) --Larry Garfield

Tim Düsterhus

1 year ago
Hi Am 2025-07-22 22:02, schrieb Larry Garfield:
> It seems the discussion has quieted down and wasn't particularly > contentious to begin with (whew), so we're just about ready for a vote.
Would it not be appropriate to answer unanswered questions in the discussion (i.e. mine from 12 days ago) and making the previously announced changes (i.e. the ones that Arnaud announced 20 days ago) to the RFC text before claiming that the discussion has quieted down and that the RFC is ready for a vote? Best regards Tim Düsterhus

Larry Garfield

1 year ago
On Tue, Jul 22, 2025, at 3:18 PM, Tim Düsterhus wrote:
> Hi > > Am 2025-07-22 22:02, schrieb Larry Garfield: >> It seems the discussion has quieted down and wasn't particularly >> contentious to begin with (whew), so we're just about ready for a vote. > > Would it not be appropriate to answer unanswered questions in the > discussion (i.e. mine from 12 days ago) and making the previously > announced changes (i.e. the ones that Arnaud announced 20 days ago) to > the RFC text before claiming that the discussion has quieted down and > that the RFC is ready for a vote? > > Best regards > Tim Düsterhus
Hm, I thought Arnaud had already addressed those, sorry. I'll have to try to replicate the debug output bits sometime tonight on the current branch to include them in the RFC text. What other changes there are unaccounted for? I thought we'd kept up on the behavioral ones. --Larry Garfield

Tim Düsterhus

1 year ago
Hi Am 2025-07-23 15:57, schrieb Larry Garfield:
> What other changes there are unaccounted for? I thought we'd kept up > on the behavioral ones.
The support for attributes (particularly #[\SensitiveParameter]) is not mentioned in the RFC either. And as I mentioned yesterday, the observer support that I specifically asked about is missing / broken. In my email from July, 10th I also had some follow-up questions / suggestions regarding the naming and the stack trace behavior, because I believe the semantics that Arnaud described are confusing. Best regards Tim Düsterhus

Larry Garfield

1 year ago
On Thu, Jul 24, 2025, at 4:54 AM, Tim Düsterhus wrote:
> Hi > > Am 2025-07-23 15:57, schrieb Larry Garfield: >> What other changes there are unaccounted for? I thought we'd kept up >> on the behavioral ones. > > The support for attributes (particularly #[\SensitiveParameter]) is not > mentioned in the RFC either. And as I mentioned yesterday, the observer > support that I specifically asked about is missing / broken. > > In my email from July, 10th I also had some follow-up questions / > suggestions regarding the naming and the stack trace behavior, because I > believe the semantics that Arnaud described are confusing. > > Best regards > Tim Düsterhus
For many of these I'm afraid I'll have to wait for Arnuad's return, as I cannot speak to them with confidence. --Larry Garfield

Tim Düsterhus

1 year ago
Hi Am 2025-07-22 22:02, schrieb Larry Garfield:
> It seems the discussion has quieted down and wasn't particularly > contentious to begin with (whew), so we're just about ready for a vote. > However, Arnaud went on vacation and didn't remember to tell me when > he'd be back. :-) So I'm going to wait a few more days just in case he > has any last minute comments, but start the vote either when he returns > or Monday the 28th, whichever comes first. (That gets the vote > complete before the deadline for 8.5.)
Besides my remarks about the contents of the RFC, I've also heard some feedback of other core developers being concerned about the complexity of the implementation, particularly at this point in the release cycle. The current PR adds about 6000 lines in total (including tests). Excluding tests it is a net addition of roughly 2000 lines of code deep in the engine: Zend/Optimizer/compact_literals.c | 1 + Zend/Optimizer/optimize_func_calls.c | 6 +- Zend/Optimizer/zend_call_graph.c | 2 + Zend/Optimizer/zend_inference.c | 1 + Zend/zend.c | 3 + Zend/zend_API.h | 3 +- Zend/zend_ast.c | 144 ++++++++++++++++- Zend/zend_ast.h | 17 ++ Zend/zend_builtin_functions.c | 4 + Zend/zend_closures.c | 50 ++++-- Zend/zend_closures.h | 3 + Zend/zend_compile.c | 220 ++++++++++++++++++++++--- Zend/zend_compile.h | 5 +- Zend/zend_execute.c | 87 ++++++---- Zend/zend_execute.h | 19 +++ Zend/zend_fibers.c | 3 + Zend/zend_language_parser.y | 27 ++-- Zend/zend_object_handlers.c | 12 +- Zend/zend_object_handlers.h | 2 +- Zend/zend_partial.c | 1237 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Zend/zend_partial.h | 67 ++++++++ Zend/zend_types.h | 4 + Zend/zend_vm_def.h | 218 ++++++++++++++++++++++++- Zend/zend_vm_execute.h | Bin 2280557 -> 2293393 bytes Zend/zend_vm_handlers.h | Bin 93176 -> 93469 bytes Zend/zend_vm_opcodes.c | Bin 9505 -> 9667 bytes Zend/zend_vm_opcodes.h | Bin 13859 -> 14051 bytes configure.ac | 1 + ext/opcache/jit/zend_jit.c | 3 + ext/opcache/jit/zend_jit_helpers.c | 12 ++ ext/opcache/jit/zend_jit_ir.c | 15 ++ ext/opcache/jit/zend_jit_vm_helpers.c | 16 +- ext/opcache/zend_file_cache.c | 4 +- ext/opcache/zend_persist.c | 1 + ext/opcache/zend_persist_calc.c | 2 + ext/reflection/php_reflection.c | 58 ++++++- ext/reflection/php_reflection.stub.php | 2 + ext/reflection/php_reflection_arginfo.h | Bin 111516 -> 111497 bytes ext/zend_test/fiber.c | 3 + win32/build/config.w32 | 2 +- 40 files changed, 2152 insertions(+), 102 deletions(-) For comparison: Clone-with was 700 lines, turning clone into a function was 200 lines, #[\NoDiscard] was 1050 lines, the (void) cast was 150 lines (all *including* tests). All these RFCs together (including tests!) are smaller than the implementation of PFA. The current proof of concept PR for PFA at https://github.com/arnaud-lb/php-src/pull/12 has 5 open ToDo items in the initial PR description and CI is red. I've tested the current version of the PR against Tideways and noticed that calls to PFA Closures are completely invisible to observers and the `zend_test` observers confirm this. For: <?php function foo(int $a, int $b, int $c, int $d): int { return array_sum([$a, $b, $c, $d]); } function main() { $f = foo(1, ?, ?, 4); $f = $f(2, ?); var_dump($f(3)); } main(); running `sapi/cli/php -d zend_test.observer.enabled=1 -d zend_test.observer.observe_all=1 test.php` outputs: <!-- init 'test.php' --> <file 'test.php'> <!-- init main() --> <main> <!-- init array_sum() --> <array_sum> </array_sum> <!-- init var_dump() --> <var_dump> int(10) </var_dump> </main> </file 'test.php'> Erroneously indicating that `array_sum()` is directly called by `main()`. The call to `foo()` is nowhere to be seen. As of now the implementation very clearly is incomplete. Given the complexity of the implementation, there is a significant risk that it cannot be stabilized until hard freeze and that “amendments” to the RFC will need to be made due to unexpected issues popping up during the finalization of the implementation and review. Best regards Tim Düsterhus

Larry Garfield

1 year ago
On Tue, Jul 22, 2025, at 3:02 PM, Larry Garfield wrote:
> Hi folks. Just a quick update: We've made one small change to the RFC. > Specifically, in order to prevent accidentally calling optional > arguments from callback locations like array_map() or array_find(), a > partial created with foo(?) will ignore any additional arguments passed > to it, and will not pass those through to the underlying function. A > partial that uses foo(?, ...) will pass through whatever it gets. > > This is mainly to avoid passing an array key from those functions to a > callback function that has an optional second parameter, which is not > intended to get a key string. In practice this is what most people > would expect would happen, but we're calling it out explicitly. (I'm > not even sure it's a behavior change from what we had before, in > practice.) > > cf: > https://wiki.php.net/rfc/partial_function_application_v2#extraneous_arguments > > It seems the discussion has quieted down and wasn't particularly > contentious to begin with (whew), so we're just about ready for a vote. > However, Arnaud went on vacation and didn't remember to tell me when > he'd be back. :-) So I'm going to wait a few more days just in case he > has any last minute comments, but start the vote either when he returns > or Monday the 28th, whichever comes first. (That gets the vote > complete before the deadline for 8.5.) > > --Larry Garfield
Hi folks. Arnaud and I have decided to hold off on the PFA vote for a while to give time to address late-arriving questions and finish off the implementation. That means it's now pushed to 8.6, and I've updated the RFC accordingly. We're not pausing though, so expect another update soon-ish. Meanwhile, if anyone else wants to weigh in on the trailing args question or repositioning parameters question, now is the time. --Larry Garfield

Tim Düsterhus

1 year ago
Hi Am 2025-06-28 07:06, schrieb Larry Garfield:
> https://wiki.php.net/rfc/partial_function_application_v2
I've now given the RFC an in-depth read. I have the following remarks: 1.
> If the function is variadic, there are two additional rules: > > - Any positional placeholders that run into the variadic portion become > required. > - If any positional placeholders run into the variadic portion, all > prior remaining placeholders become required. However, those parameters > may not be called with named arguments, as there is no name to use.
I do not understand what “running into the variadic portion” means. An example would probably be helpful. The first bullet point is probably intended to mean the following: function foo($a, ...$b) { } $pfa = foo(?, ?, ?, ?); Now `$pfa` has 4 required parameters, despite `foo()` only having 1? But I don't understand what the second bullet point is intended to say. 2. In the placeholder semantics section:
> While in theory that means a call like this would be legal:
From what I see this specific case is not part of the examples section either. Including the desugaring of this extreme case would certainly help understanding. 3. In the “Examples” section: The choice of parameters names makes it hard to understand the examples, particularly when named parameters are used to provide arguments out of order. Including the “position” in the name of the parameter would make it easier to follow the example, since it is not necessary to look up the signature of `stuff()` all the time. (it becomes manageable if you read it as a sentence "is FPM") 4. In the “Examples” section: Is the “thunk” example accurate? It's the only example where the resulting “PFA” includes a variadic picking up the remaining arguments. 5. In the “Examples” section:
> $c = stuff(?, p: $point, f: ?, s: ?, m: 4);
It is unexpected to me that this definition will take `$s` before `$f` in the resulting Closure. The explanation says that the “order does not matter”, but it is certainly unexpected that the order *changes* during desugaring. In fact, the "(four(c: ?, d: 4, b: ?, a: 1))(2, 3);" example further down below seems to contradict this. 6. In the “Extraneous arguments” section: I disagree with silently ignoring trailing arguments, because it *adds* to the existing inconsistency between userland and internal functions. In fact there was a recent-ish discussion (I would provide a link, but can't find it right now) about allowing userland functions to define that they want to be called with a strict arity. While it would add to the symbol soup, having an explicit “ignore the argument at this position” indicator would be useful. For example: `$firstNonZero = array_find($arr, intval(?, _));` (I understand this specific one doesn't work, because `_` is a valid constant). 7. It is not clear to me from the RFC, why the `...` placeholder to indicate “all remaining arguments” must come between positional and named parameters. It would make more sense to me for it to become last, because it would also make it clearer that named parameters take priority over `...`. Is there some technical limitation that caused this choice to be made? Best regards Tim Düsterhus

Larry Garfield

1 year ago
On Wed, Jul 23, 2025, at 3:56 AM, Tim Düsterhus wrote:
> Hi > > Am 2025-06-28 07:06, schrieb Larry Garfield: >> https://wiki.php.net/rfc/partial_function_application_v2 > > I've now given the RFC an in-depth read. I have the following remarks: > > 1. > >> If the function is variadic, there are two additional rules: >> >> - Any positional placeholders that run into the variadic portion become >> required. >> - If any positional placeholders run into the variadic portion, all >> prior remaining placeholders become required. However, those parameters >> may not be called with named arguments, as there is no name to use. > > I do not understand what “running into the variadic portion” means. An > example would probably be helpful. The first bullet point is probably > intended to mean the following: > > function foo($a, ...$b) { } > $pfa = foo(?, ?, ?, ?); > > Now `$pfa` has 4 required parameters, despite `foo()` only having 1? > > But I don't understand what the second bullet point is intended to say.
I have added the following example: function foo(int $a = 5, int $b = 1, string ...$c) { } $pfa = foo(?, ?, ?, ?); // Equivalent to this: // Note that $a and $b become required, because there must be at least 4 arguments. $pfa = fn(int $a, int $b, string $c1, string $c2) => foo($a, $b, $c1, $c2);
> 2. > > In the placeholder semantics section: > >> While in theory that means a call like this would be legal: > > From what I see this specific case is not part of the examples section > either. Including the desugaring of this extreme case would certainly > help understanding.
I have added the following equivalent: $c = fn(string $s, Point $p, int $m = 0) => stuff(1, $s, 3.14, $m);
> 3. > > In the “Examples” section: > > The choice of parameters names makes it hard to understand the examples, > particularly when named parameters are used to provide arguments out of > order. Including the “position” in the name of the parameter would make > it easier to follow the example, since it is not necessary to look up > the signature of `stuff()` all the time. (it becomes manageable if you > read it as a sentence "is FPM")
LOL. That was completely unintentional. :-) However, I have gone through and added numbers to the variable names to clarify their original ordering.
> 4. > > In the “Examples” section: > > Is the “thunk” example accurate? It's the only example where the > resulting “PFA” includes a variadic picking up the remaining arguments.
Hm. I think you're right, with the Extraneous Args section's clarification, the others should likely have a trailing ...$args as well. I will clarify with Arnaud when he returns and update accordingly.
> 5. > > In the “Examples” section: > >> $c = stuff(?, p: $point, f: ?, s: ?, m: 4); > > It is unexpected to me that this definition will take `$s` before `$f` > in the resulting Closure. The explanation says that the “order does not > matter”, but it is certainly unexpected that the order *changes* during > desugaring. > > In fact, the "(four(c: ?, d: 4, b: ?, a: 1))(2, 3);" example further > down below seems to contradict this.
The order is determined by the original function. The same is true for a normal function call. function foo(int $a, int $b, int $c) {} // All of these are equivalent. foo(a: 1, b: 2, c: 3); foo(b: 2, a: 1, c: 3); foo(c: 3, b: 2, a: 1); foo(1, 2, 3); So the same is true of a PFA: foo(a: ?, b: 2, c: ?); foo(b: 2, a: ?, c: ?); foo(c: ?, b: 2, a: ?); foo(?, 2, ?); All of those produce the same result.
> 6. > > In the “Extraneous arguments” section: > > I disagree with silently ignoring trailing arguments, because it *adds* > to the existing inconsistency between userland and internal functions. > In fact there was a recent-ish discussion (I would provide a link, but > can't find it right now) about allowing userland functions to define > that they want to be called with a strict arity. > > While it would add to the symbol soup, having an explicit “ignore the > argument at this position” indicator would be useful. For example: > `$firstNonZero = array_find($arr, intval(?, _));` (I understand this > specific one doesn't work, because `_` is a valid constant). > > 7. > > It is not clear to me from the RFC, why the `...` placeholder to > indicate “all remaining arguments” must come between positional and > named parameters. It would make more sense to me for it to become last, > because it would also make it clearer that named parameters take > priority over `...`. Is there some technical limitation that caused this > choice to be made?
I can't find an answer to that in my notes, so I'll have to defer to Arnuad when he returns. (Ilija tells me he's back Monday.) I think so, but I'm not certain. --Larry Garfield

Tim Düsterhus

1 year ago
Hi I did not yet have the time to check the changes and your reply in detail, but I already wanted to follow-up on some of the points. Am 2025-07-23 15:55, schrieb Larry Garfield:
> $c = fn(string $s, Point $p, int $m = 0) => stuff(1, $s, 3.14, $m);
I don't think this is accurate? `$p` is unused.
>> The choice of parameters names makes it hard to understand the >> examples, >> particularly when named parameters are used to provide arguments out >> of >> order. Including the “position” in the name of the parameter would >> make >> it easier to follow the example, since it is not necessary to look up >> the signature of `stuff()` all the time. (it becomes manageable if you >> read it as a sentence "is FPM") > > LOL. That was completely unintentional. :-) > > However, I have gone through and added numbers to the variable names to > clarify their original ordering.
I believe you missed some. I'm still seeing a bare `$i` and also a `$4p` typo. Please double-check, possibly there are also other mistakes, I didn't check in detail.
> So the same is true of a PFA:
I don't think that necessarily follows, because for PFAs there are two parameter lists that are relevant: 1. The parameter list of the resulting Closure. 2. The parameter list of the original function.
> foo(a: ?, b: 2, c: ?); > foo(b: 2, a: ?, c: ?); > foo(c: ?, b: 2, a: ?); > foo(?, 2, ?); > > All of those produce the same result.
I don't think they should. Specifically the (1) and (3) should not. My expectation is that: $f = foo(a: ?, b: 2, c: ?); $f(1, 3); // calls foo(1, 2, 3); and $f = foo(c: ?, b: 2, a: ?); $f(1, 3); // calls foo(3, 2, 1); The order of the question marks should match the order of the parameters in the resulting Closure, which is not necessarily the order of the order parameters of the original function. Best regards Tim Düsterhus

Tim Düsterhus

1 year ago
Hi Am 2025-07-24 12:03, schrieb Tim Düsterhus:
> I don't think they should. Specifically the (1) and (3) should not. My > expectation is that: > > $f = foo(a: ?, b: 2, c: ?); > $f(1, 3); // calls foo(1, 2, 3); > > and > > $f = foo(c: ?, b: 2, a: ?); > $f(1, 3); // calls foo(3, 2, 1); > > The order of the question marks should match the order of the > parameters in the resulting Closure, which is not necessarily the order > of the order parameters of the original function.
To add to that: By respecting the order of question marks as written, it would also make PFA easier to understand and also more powerful. After sending the email earlier, I came across this doctrine/collections PR, while reviewing some dependency upgrades: https://github.com/doctrine/collections/pull/424 When migrating the `findFirst()` method from a custom foreach loop to `array_find()`, they couldn't pass along the given callable directly, since Doctrine opted for a different parameter order. Thus they needed an intermediate Closure to swap the arguments. If the argument names for the function are known (which they are not in the Doctrine example, as it's an arbitrary Closure that is given), this would allow to swap arguments as necessary: function cb($key, $val) { … } array_find($array, cb(val: ?, key: ?)); // array_find expects $value, $key. Best regards Tim Düsterhus

Larry Garfield

1 year ago
On Thu, Jul 24, 2025, at 6:52 AM, Tim Düsterhus wrote:
> Hi > > Am 2025-07-24 12:03, schrieb Tim Düsterhus: >> I don't think they should. Specifically the (1) and (3) should not. My >> expectation is that: >> >> $f = foo(a: ?, b: 2, c: ?); >> $f(1, 3); // calls foo(1, 2, 3); >> >> and >> >> $f = foo(c: ?, b: 2, a: ?); >> $f(1, 3); // calls foo(3, 2, 1); >> >> The order of the question marks should match the order of the >> parameters in the resulting Closure, which is not necessarily the order >> of the order parameters of the original function.
That would be inconsistent with how named arguments work anywhere else. With a regular function call, you can list named args in any order you feel like and the engine will reorder them for you back to the original defined order. (My IDE keeps yelling at me to put them in order, but that's an IDE problem, not a language problem.) Having PFA behave differently feels needlessly confusing, especially since positional placeholders are supported, too. $f = foo(?, a: ?, b: ?) It's not obvious to me what should happen there. Does that implicitly mean $c is now the first argument? That's not at all apparent from the syntax. Similarly, in a longer example: foo ($a, $b, $c, $d, $e, $f); $f = foo(?, 3, b: ?, e: ?, ...); That already takes a moment of thought to know what's going on, despite being legal. (A value is provided for $c, but nothing else.) Having that also change the order to... I think it would be f($a, $b, $e, $d, $f) ? Just raises the effort to grok it even further. The only value to breaking existing convention and reordering parameters I can see would be:
> To add to that: By respecting the order of question marks as written, it > would also make PFA easier to understand and also more powerful. After > sending the email earlier, I came across this doctrine/collections PR, > while reviewing some dependency upgrades: > https://github.com/doctrine/collections/pull/424 > > When migrating the `findFirst()` method from a custom foreach loop to > `array_find()`, they couldn't pass along the given callable directly, > since Doctrine opted for a different parameter order. Thus they needed > an intermediate Closure to swap the arguments. If the argument names for > the function are known (which they are not in the Doctrine example, as > it's an arbitrary Closure that is given), this would allow to swap > arguments as necessary: > > function cb($key, $val) { … } > > array_find($array, cb(val: ?, key: ?)); // array_find expects > $value, $key.
Which is valid, but on balance I think it's OK for that case to still be a manual short-closure in exchange for less confusing behavior of PFA. In practice, I expect most PFA uses to be creating unary function anyway, followed by thunks, so it won't greatly matter. Does anyone else agree/disagree? --Larry Garfield

Tim Düsterhus

1 year ago
Hi On 7/24/25 16:04, Larry Garfield wrote:
>>> I don't think they should. Specifically the (1) and (3) should not. My >>> expectation is that: >>> >>> $f = foo(a: ?, b: 2, c: ?); >>> $f(1, 3); // calls foo(1, 2, 3); >>> >>> and >>> >>> $f = foo(c: ?, b: 2, a: ?); >>> $f(1, 3); // calls foo(3, 2, 1); >>> >>> The order of the question marks should match the order of the >>> parameters in the resulting Closure, which is not necessarily the order >>> of the order parameters of the original function. > > That would be inconsistent with how named arguments work anywhere else. With a regular function call, you can list named args in any order you feel like and the engine will reorder them for you back to the original defined order. (My IDE keeps yelling at me to put them in order, but that's an IDE problem, not a language problem.) Having PFA behave differently feels needlessly confusing, especially since positional placeholders are supported, too.
The point is that PFA defines not just a function *call*, but also a function *definition* at the same time. This is what I meant by “you are dealing with two parameter lists”. What you correctly describe is the behavior of named arguments in a function *call*. What we do not have yet is the behavior for a function *definition* and I'm arguing that the proposed behavior in the RFC is wrong. Using the existing example of: function foo(int $a, int $b, int $c) {} For the first case of: $f = foo(a: ?, b: 2, c: ?); My mental model desugars that to: $f = fn ($a, $c) => foo(a: $a, b: 2, c: $c); And for the second case: $f = foo(c: ?, b: 2, a: ?); it desugars to: $f = fn ($c, $a) => foo(c: $c, b: 2, a: $a); The order of the named parameters in the inner function call to `foo()` changed, but as you said, the engine will make sure to place them into the appropriate parameters, resulting in a successful call to `foo()`. But it does not follow that the parameters of the PFA Closure also need to be reordered, they can (and should) be completely independent, because PFA allows to “call named parameters using positional arguments”. Basically when I'm reading the signature: $f = foo(c: ?, b: 2, a: ?); $f(1, 3); I'm reading the question-marks from “left to right” in positional order and then when I call `$f` using positional arguments, I'm expecting the question-marks to be filled in in positional order. ------ I've also asked a friend of mine, who had only skimmed the RFC a while ago, without explaining anything: function foo($a, $b, $c) { var_dump($a, $b, $c); } $f = foo(c: ?, b: 2, a: ?); $f(1, 3); What would your intuitive expectation what this would output? They answered "3 2 1" and after I clarified that the RFC specifies this as "1 2 3", their first reaction was "What?", followed by "there are two placeholders, they are filled in left-to-right".
> > $f = foo(?, a: ?, b: ?) > > It's not obvious to me what should happen there. Does that implicitly mean $c is now the first argument? That's not at all apparent from the syntax.
I believe this should be a compiler error, because `$a` is specified twice. At least it would be for a normal function call: https://3v4l.org/oVMLC
> Similarly, in a longer example: > > foo ($a, $b, $c, $d, $e, $f); > > $f = foo(?, 3, b: ?, e: ?, ...); > > That already takes a moment of thought to know what's going on, despite being legal. (A value is provided for $c, but nothing else.) Having that also change the order to... I think it would be f($a, $b, $e, $d, $f) ? Just raises the effort to grok it even further.
Likewise `b` is specified twice. Best regards Tim Düsterhus

Larry Garfield

1 year ago
On Thu, Jul 24, 2025, at 5:03 AM, Tim Düsterhus wrote:
> Hi > > I did not yet have the time to check the changes and your reply in > detail, but I already wanted to follow-up on some of the points. > > Am 2025-07-23 15:55, schrieb Larry Garfield: >> $c = fn(string $s, Point $p, int $m = 0) => stuff(1, $s, 3.14, $m); > > I don't think this is accurate? `$p` is unused. > >> However, I have gone through and added numbers to the variable names to >> clarify their original ordering. > > I believe you missed some. I'm still seeing a bare `$i` and also a `$4p` > typo. Please double-check, possibly there are also other mistakes, I > didn't check in detail.
Thanks, I think I got them all now. (Reply to the rest in the other reply.) --Larry Garfield

Larry Garfield

327 days ago
On Sat, Jun 28, 2025, at 12:06 AM, Larry Garfield wrote:
> Hi folks. Arnaud and I would like to present take-2 at Partial > Function Application. > > https://wiki.php.net/rfc/partial_function_application_v2 > > It is largely similar to the previous PFA proposal from 2021, though > there are a number of changes. Most notably: > > * The implementation is simpler, because FCC already did part of the > work. This RFC can build on it. > * Constructors are not supported. > * But optional arguments and named placeholders are supported. > * It includes pipe-based optimizations. > > Note: We realize that this is a non-trivial RFC coming late in the > cycle. We are proposing it now because, well, it's ready now. If the > discussion goes smoothly, we're OK calling a vote on it for 8.5, > especially as it would complement pipes so well. If the discussion > runs longer, we're also OK with targeting 8.6 instead. We'll see how > that goes. > > <floor opens for discussion, Larry falls through the trap door>
Hi folks. PFA is back. :-) Since our last episode, Arnaud has greatly revised the implementation. Rather than emulating a closure and all the associated behavior, the new approach compiles PFAs into normal closures at runtime, leveraging opcache. The resulting closure is then "just a closure," and will behave like any other. That means, for instance, its behavior in debugging, reflection, etc. is all self-evident. There were a few small behavior changes as a result, but not dramatically. Mainly it impacted variadic cases, where parameter names are now auto-generated when appropriate rather than simply being unnamed. We have also changed the order of placeholders from "positional, variadic, named" to "positional, named, variadic." Meaning the `...` "and the rest" placeholder is always at the end of the call. One outstanding question is whether to allow reordering of parameters in the PFA closure by using named arguments. With this implementation, Arnaud says it's possible to do if we decide to. I am still concerned that it would create too much complexity and confusion in practice. But we're willing to go with a broad consensus if it emerges. https://wiki.php.net/rfc/partial_function_application_v2 --Larry Garfield

Tim Düsterhus

326 days ago
Hi Am 2025-10-09 20:54, schrieb Larry Garfield:
> One outstanding question is whether to allow reordering of parameters > in the PFA closure by using named arguments. With this implementation, > Arnaud says it's possible to do if we decide to. I am still concerned > that it would create too much complexity and confusion in practice. > But we're willing to go with a broad consensus if it emerges.
I still believe that this is *less* confusing, as I outlined before.
> https://wiki.php.net/rfc/partial_function_application_v2
I've given it another read. Remarks:
> Positional placeholders that map to the variadic portion of the > underlying function will be named $args0, $args1, etc., and may be > called by name if desired.
What will happen if the original function already has a parameter named $args0?
> stuff($1i, $s2, $f3, $p4, $m5);
Typo: 1i.
> Prefill all parameters, making a "delayed call" or "thunk"
The example desugaring is inconsistent with the previously explained semantics: Here an arbitrary number of arguments is accepted, but previously it says "will result in a Closure with no parameters".
> // Placeholders may be named, too. Their order doesn't > // matter as long as they come after the ..., if any.
I believe this example is outdated, since the ... must come last now. (the 's' and 'i' parameters are also missing the "number").
> $c = fn(int $i1, ?float $f3, Point $p1, Point $p2): string => > things($i1, $f3, $p1, $p2);
This example desugaring seems to be incorrect with regard to the naming of the variadic parameters.
> (four(c: ?, d: 4, b: ?, a: 1))(2, 3);
Just to spell this out explicitly: I believe this should print "1, 3, 2, 4".
> $eMaker = fn(int y): E => E::make(1, $y);
`$` missing before `y`.
> $c = stuff(?, ?, ?, ?, ?, ?);
The error message for this one appears to be a copy and paste error.
> // throws Error(Named parameter $i overwrites previous place holder)
Is it actually "place holder" with a space?
> $c = stuff(i:1, ?, ?, ?, ?);
Minor formatting nit: Missing space after colon.
> $inter = fn(mixed ...$args): int => intval(..$args);
Typo: Missing dot in splat. ------ Questions: - Are PFAs legal in constant expressions? e.g. does the following work: `const Foo = intval(?, 10);`? - The RFC says that it compiles down to the equivalent Closure. Can you add an example of a stack trace for completeness? Ideally one with SensitiveParameter working. That will then showcase the closure naming, SensitiveParameters and that there are two stack frames for the call. Best regards Tim Düsterhus

Larry Garfield

326 days ago
On Thu, Oct 9, 2025, at 4:39 PM, Tim Düsterhus wrote:
> I've given it another read. Remarks: > >> Positional placeholders that map to the variadic portion of the >> underlying function will be named $args0, $args1, etc., and may be >> called by name if desired. > > What will happen if the original function already has a parameter named > $args0?
It will skip over existing names. I've updated the text accordingly.
>> Prefill all parameters, making a "delayed call" or "thunk" > > The example desugaring is inconsistent with the previously explained > semantics: Here an arbitrary number of arguments is accepted, but > previously it says "will result in a Closure with no parameters".
Hm, I think that should probably read "no required parameters". It would only make a difference if the underlying function had an optional variadic AND you called the thunk with extra, extraneous args. *Various typos fixed along the way*
> ------ > > Questions: > > - Are PFAs legal in constant expressions? e.g. does the following work: > `const Foo = intval(?, 10);`?
At the moment no, though Arnaud says that should be doable, with some reasonable restrictions. (No variables, no variable callable name, etc.)
> - The RFC says that it compiles down to the equivalent Closure. Can you > add an example of a stack trace for completeness? Ideally one with > SensitiveParameter working. That will then showcase the closure naming, > SensitiveParameters and that there are two stack frames for the call. > > Best regards > Tim Düsterhus
I've asked Arnaud to generate one for the RFC. --Larry Garfield

Tim Düsterhus

324 days ago
Hi On 10/10/25 17:26, Larry Garfield wrote:
>> What will happen if the original function already has a parameter named >> $args0? > > It will skip over existing names. I've updated the text accordingly.
Okay. Looking at all the examples, I think it would be nice if it would not use the generic `arg` prefix, but use the original name as the prefix. For the function foo(int $a = 5, int $b = 1, string ...$c) { } example, the 3rd and following parameters could be `$c_1`, `$c_2`, … (still skipping over duplicates).
>>> Prefill all parameters, making a "delayed call" or "thunk" >> >> The example desugaring is inconsistent with the previously explained >> semantics: Here an arbitrary number of arguments is accepted, but >> previously it says "will result in a Closure with no parameters". > > Hm, I think that should probably read "no required parameters". It would only make a difference if the underlying function had an optional variadic AND you called the thunk with extra, extraneous args.
Okay. Now looking at the examples: $c = stuff(?, ?, f3: 3.5, p4: $point, ...); $c = fn(int $i1, string $s2, int $m5 = 0): string => stuff($i1, $s2, 3.5, $point, $m5); and $c = stuff(1, 'hi', 3.4, $point, 5, ...); $c = fn(...$args): string => stuff(1, 'hi', 3.4, $point, 5, ...$args); seem to be inconsistent with each other with regard to whether or not "superfluous" arguments are passed through. This might (or might not?) be explained in the “func_get_args() and friends” section, but is unexplained at the point the example appears. Also within the func_get_args() section, there is no explicit `...$args` in the resulting signature, but instead the desugaring uses array_slice() + func_get_args().
>> - Are PFAs legal in constant expressions? e.g. does the following work: >> `const Foo = intval(?, 10);`? > > At the moment no, though Arnaud says that should be doable, with some reasonable restrictions. (No variables, no variable callable name, etc.)
Yes, restrictions go without saying. The support for FCC in const-expr also doesn't support variable names and Closures in const-expr may also not capture the scope.
> I've asked Arnaud to generate one for the RFC.
Thank you, I'm seeing you added an example. I've a small request to hopefully make the example more useful: It currently uses only “placeholder” parameters. It would be useful to also add one pre-filled parameter to it. I would assume that this pre-filled parameter does not appear within the Closure frame, but appears with the frame for the actual function? Relatedly to the stack trace example and also to the “Evaluation order” I'd be curious how pre-filled parameters are implemented technically. For pre-filled variables, the regular variable capturing logic would work, but it doesn't for function returns. Is it effectively creating a temporary variable under the hood? Meaning is: $partial = speak(?, getArg()); desugared into: $pfa_tmp_2 = getArg(); $partial = fn (string $who) => speak($who, $pfa_tmp2); ? - How does it interact with `compact()`, specifically: $partial = compact(someValue(), ?); Would the `?` be able to capture a variable containing the return value of `someValue()`? - How does it interact with `ReflectionFunctionAbstract::getClosureUsedVariables()`? - Same question for other scope introspection functionality (e.g. Xdebug). Best regards Tim Düsterhus

Arnaud.lb

323 days ago
Hi Tim, On Sun, Oct 12, 2025 at 8:36 PM Tim Düsterhus <tim@bastelstu.be> wrote:
> Relatedly to the stack trace example and also to the “Evaluation order” > I'd be curious how pre-filled parameters are implemented technically. > For pre-filled variables, the regular variable capturing logic would > work, but it doesn't for function returns. Is it effectively creating a > temporary variable under the hood? Meaning is: > > $partial = speak(?, getArg()); > > desugared into: > > $pfa_tmp_2 = getArg(); > $partial = fn (string $who) => speak($who, $pfa_tmp2); > > ?
Pre-filled parameters are passed to the Closure via used vars, but there is no requirement for them to be in a CV slot so we don't need to create a temporary variable. We can bind used vars to any zval directly. The code $partial = speak(?, getArg()); Is compiled to INIT_FCALL speak SEND_PLACEHOLDER INIT_FCALL getArg T1 = DO_FCALL SEND T1 CALLABLE_CONVERT_PARTIAL The CALLABLE_CONVERT_PARTIAL opcode generates the AST for function (string $who) use ($msg) { return \speak($who, $msg); }; and compiles it (this is cached in SHM). It then instantiates the Closure, and binds $msg to ZEND_CALL_ARG(2). A few other values are passed via used vars, such as the object if it's an instance method call, or the underlying function if it's a closure.
> - How does it interact with > `ReflectionFunctionAbstract::getClosureUsedVariables()`? > - Same question for other scope introspection functionality (e.g. Xdebug).
getClosureUsedVariables() and other introspection functionality will report any used var as usual. Used vars are named after the parameter if there is no collision. But this should not be considered API as this may change. For instance, I want to optimize-out used vars for parameters that are pre-filled with a literal. It would be possible to hide used vars of a PFA in Reflection. Best Regards, Arnaud

Tim Düsterhus

319 days ago
Hi Am 2025-10-13 19:00, schrieb Arnaud Le Blanc:
>> - How does it interact with >> `ReflectionFunctionAbstract::getClosureUsedVariables()`? >> - Same question for other scope introspection functionality (e.g. >> Xdebug). > > getClosureUsedVariables() and other introspection functionality will > report any used var as usual. Used vars are named after the parameter > if there is no collision. But this should not be considered API as > this may change. For instance, I want to optimize-out used vars for > parameters that are pre-filled with a literal. It would be possible to > hide used vars of a PFA in Reflection.
Understood, thank you for the clarification. Please add this information to the RFC (ideally with examples of the possible cases, e.g. the passing of the `Closure` object). It's fine if you explicitly say “we consider this an implementation detail, but this is how it currently works”. This is also useful information to have if a user reports a bug in PFA that relates to this, so that we can tell them “what you are doing is not guaranteed to work, please don't”. Best regards Tim Düsterhus

Larry Garfield

323 days ago
On Sun, Oct 12, 2025, at 1:35 PM, Tim Düsterhus wrote:
> Hi > > On 10/10/25 17:26, Larry Garfield wrote: >>> What will happen if the original function already has a parameter named >>> $args0? >> >> It will skip over existing names. I've updated the text accordingly. > > Okay. Looking at all the examples, I think it would be nice if it would > not use the generic `arg` prefix, but use the original name as the > prefix. For the > > function foo(int $a = 5, int $b = 1, string ...$c) { } > > example, the 3rd and following parameters could be `$c_1`, `$c_2`, … > (still skipping over duplicates).
OK, there was a miscommunication between Arnaud and I. It is using the variable name, not "args" already. Just with no _ and 0-based. I've updated the RFC accordingly.
> Okay. Now looking at the examples: > > $c = stuff(?, ?, f3: 3.5, p4: $point, ...); > $c = fn(int $i1, string $s2, int $m5 = 0): string => stuff($i1, > $s2, 3.5, $point, $m5); > > and > > $c = stuff(1, 'hi', 3.4, $point, 5, ...); > $c = fn(...$args): string => stuff(1, 'hi', 3.4, $point, 5, ...$args); > > seem to be inconsistent with each other with regard to whether or not > "superfluous" arguments are passed through. > > This might (or might not?) be explained in the “func_get_args() and > friends” section, but is unexplained at the point the example appears. > Also within the func_get_args() section, there is no explicit `...$args` > in the resulting signature, but instead the desugaring uses > array_slice() + func_get_args().
We talked a bit more, and decided to tighten the rules further. I've updated the RFC accordingly. Essentially, the first example is correct, the second has been changed. If the underlying function is variadic, and ... is used in the PFA, then it will accept an arbitrary number of arguments. In any other case, only explicitly-specified arguments will be passed through.
> Thank you, I'm seeing you added an example. I've a small request to > hopefully make the example more useful: It currently uses only > “placeholder” parameters. It would be useful to also add one pre-filled > parameter to it. I would assume that this pre-filled parameter does not > appear within the Closure frame, but appears with the frame for the > actual function?
Updated the error dump example.
> - How does it interact with `compact()`, specifically: > > $partial = compact(someValue(), ?); > > Would the `?` be able to capture a variable containing the return value > of `someValue()`?
Good question! compact(), extract(), etc. can't actually work with PFA, because they operate on the ambient context, which PFA by design changes. I've updated the RFC to note three incompatible core functions. (The other is func_get_arg()). --Larry Garfield

Tim Düsterhus

322 days ago
Hi I'll give the RFC another read at a later point, but I wanted to get something out before I forget: Am 2025-10-13 20:23, schrieb Larry Garfield:
>> - How does it interact with `compact()`, specifically: >> >> $partial = compact(someValue(), ?); >> >> Would the `?` be able to capture a variable containing the return >> value >> of `someValue()`? > > Good question! compact(), extract(), etc. can't actually work with > PFA, because they operate on the ambient context, which PFA by design > changes. I've updated the RFC to note three incompatible core > functions. (The other is func_get_arg()).
- `get_defined_vars()` should be added to the list (possibly more?). Or to make things easier: Just say everything that doesn't already work with FCC will not work with PFA either. - What will happen with `assert(?)`? Assert is special in that it captures the AST at compile time to render it in the error message. So specifically: $x = assert(?); assert(false); // How will the AssertionError look like? Best regards Tim Düsterhus

Larry Garfield

322 days ago
On Mon, Oct 13, 2025, at 4:00 PM, Tim Düsterhus wrote:
> Hi > > I'll give the RFC another read at a later point, but I wanted to get > something out before I forget: > > Am 2025-10-13 20:23, schrieb Larry Garfield: >>> - How does it interact with `compact()`, specifically: >>> >>> $partial = compact(someValue(), ?); >>> >>> Would the `?` be able to capture a variable containing the return >>> value >>> of `someValue()`? >> >> Good question! compact(), extract(), etc. can't actually work with >> PFA, because they operate on the ambient context, which PFA by design >> changes. I've updated the RFC to note three incompatible core >> functions. (The other is func_get_arg()). > > - `get_defined_vars()` should be added to the list (possibly more?). Or > to make things easier: Just say everything that doesn't already work > with FCC will not work with PFA either. > - What will happen with `assert(?)`? Assert is special in that it > captures the AST at compile time to render it in the error message. So > specifically: > > $x = assert(?); > assert(false); // How will the AssertionError look like? > > Best regards > Tim Düsterhus
I've added assert() and get_defined_vars() to the list, and relabeled it to be incomplete. I also added a note about FCC and it being the same list. --Larry Garfield

Tim Düsterhus

319 days ago
Hi Am 2025-10-14 15:29, schrieb Larry Garfield:
> I've added assert() and get_defined_vars() to the list, and relabeled > it to be incomplete. I also added a note about FCC and it being the > same list.
assert() *is* compatible with FCC: <?php $f = assert(...); $f(1 < 0); This will print: Fatal error: Uncaught AssertionError in /tmp/test.php:3 without any stringified expression. I assume it will be the same with PFA then? Or will that print something like this: Uncaught AssertionError: assert(?) in /tmp/test.php:4 -------------------- Some more notes: 1. The desugaring in the RFC shows non-static Closures only. This makes sense to me for simplicity. The RFC should however explicitly specify whether or not the resulting Closures will be static Closures or not (or which conditions need to folks for them to be static). This is important to know, because non-static Closures might keep objects alive for longer than necessary or expected. 2. I am noticing that the error messages are quite inconsistent with existing error messages: not enough arguments or placeholders for application of stuff, 1 given and at least 4 expected" vs json_encode() expects at least 1 argument, 0 given So it should probably be: Partial application of json_encode() expects at least 1 argument or placeholder, 0 given or something similar. Can you please double-check all the error messages in the implementation for consistency with existing error messages? Best regards Tim Düsterhus

Tim Düsterhus

319 days ago
Hi Am 2025-10-13 20:23, schrieb Larry Garfield:
> OK, there was a miscommunication between Arnaud and I. It is using the > variable name, not "args" already. Just with no _ and 0-based. I've > updated the RFC accordingly.
Thank you! I noticed a small typo in one of the updated examples: $c = fn(int $i1, ?float $f3, Point $points0, Point $points`): string => things($i1, $f3, $points0, $points1); The second points parameter is misnamed.
> We talked a bit more, and decided to tighten the rules further. I've > updated the RFC accordingly. Essentially, the first example is > correct, the second has been changed. > > If the underlying function is variadic, and ... is used in the PFA, > then it will accept an arbitrary number of arguments. In any other > case, only explicitly-specified arguments will be passed through.
The RFC still says: // Whereas strictly speaking, a variadic placeholder is equivalent to this: $f = foo(1, ...); $f = fn(int $b, int $c): int => foo(1, $b, $c, $d, ...array_slice(func_get_args(), 3)); Did you miss updating that? This also makes me realize that this means that PFA will not actually be a strict superset of FCC, no? FCC always passes through all the extra arguments, whereas PFA will not (at least based on your reply). Best regards Tim Düsterhus

=?UTF-8?Q?Joshua_R=C3=BCsweg?=

317 days ago
> On 9. Oct 2025, at 20:54, Larry Garfield <larry@garfieldtech.com> wrote: > > One outstanding question is whether to allow reordering of parameters in the PFA closure by using named arguments. With this implementation, Arnaud says it's possible to do if we decide to. I am still concerned that it would create too much complexity and confusion in practice. But we're willing to go with a broad consensus if it emerges.
Hi I also find it confusing if I define a different parameter order in the partial function and then this order is automatically overridden to the original order rather than being used. When I consciously change the parameter order, I expect this order to be preserved in the resulting function. That's the behavior I would expect as a developer. Cheers Joshua Rüsweg

Larry Garfield

306 days ago
On Thu, Oct 9, 2025, at 1:54 PM, Larry Garfield wrote:
> Hi folks. PFA is back. :-) > > Since our last episode, Arnaud has greatly revised the implementation. > Rather than emulating a closure and all the associated behavior, the > new approach compiles PFAs into normal closures at runtime, leveraging > opcache. The resulting closure is then "just a closure," and will > behave like any other. That means, for instance, its behavior in > debugging, reflection, etc. is all self-evident. > > There were a few small behavior changes as a result, but not > dramatically. Mainly it impacted variadic cases, where parameter names > are now auto-generated when appropriate rather than simply being > unnamed. > > We have also changed the order of placeholders from "positional, > variadic, named" to "positional, named, variadic." Meaning the `...` > "and the rest" placeholder is always at the end of the call. > > One outstanding question is whether to allow reordering of parameters > in the PFA closure by using named arguments. With this implementation, > Arnaud says it's possible to do if we decide to. I am still concerned > that it would create too much complexity and confusion in practice. > But we're willing to go with a broad consensus if it emerges. > > https://wiki.php.net/rfc/partial_function_application_v2 > > --Larry Garfield
Another set of updates. * Some improvements to the error handling and error messages. * Clarified that PFA is compatible with constant expressions. * Clarified when the resulting closure is static or not. The main change is we revisited how extra arguments are handled. Long story short, they now forward only if the PFA has a ..., and how they forward depends on if the underlying function is variadic or not. The RFC now includes more precise examples of how the desugaring works, including the variadic/func_get_args cases, which should make it all a lot clearer. The one outstanding question is whether we allow reordering using named arguments or preserve the underlying order. Arnaud says either one is doable. So far, only 2 people have commented on it (favoring reordering). We still want feedback from more people to see if there really is a consensus one way or another. (2 people is too small a sample size to draw any conclusions.) Also, if anyone not named Tim would like to weigh in, now is the time. :-) --Larry Garfield

Niels Dossche

306 days ago
On 30/10/2025 19:44, Larry Garfield wrote:
> The one outstanding question is whether we allow reordering using named arguments or preserve the underlying order. Arnaud says either one is doable. So far, only 2 people have commented on it (favoring reordering). We still want feedback from more people to see if there really is a consensus one way or another. (2 people is too small a sample size to draw any conclusions.) > > Also, if anyone not named Tim would like to weigh in, now is the time. :-)
Hi I'm not named Tim. Reordering makes the most sense and is what I intuitively expect to happen. Kind regards

Volker Dusch

305 days ago
On Thu, Oct 30, 2025 at 7:46 PM Larry Garfield <larry@garfieldtech.com> wrote:
> > On Thu, Oct 9, 2025, at 1:54 PM, Larry Garfield wrote: > The one outstanding question is whether we allow reordering using named arguments or preserve the underlying order. Arnaud says either one is doable. So far, only 2 people have commented on it (favoring reordering). We still want feedback from more people to see if there really is a consensus one way or another. (2 people is too small a sample size to draw any conclusions.)
> Also, if anyone not named Tim would like to weigh in, now is the time. :-)
For what it's worth: ``` function f($a, $b, $c) { echo "$a-$b-$c"; } $f = f(c: ?, b: ?, a: ?); $f(1, 2, 3); ``` I expect this to print 3-2-1, intuitively. "c" is the new first parameter in the "function definition" for me. So count me as "reordering". Kind Regards, Volker

Dennis Birkholz

304 days ago
Hi Am 30.10.25 um 8:53 PM schrieb Volker Dusch:
> ``` > function f($a, $b, $c) { echo "$a-$b-$c"; } > > $f = f(c: ?, b: ?, a: ?); > $f(1, 2, 3); > ``` > > I expect this to print 3-2-1, intuitively.
That is what I would expect, too. Kind regards Dennis

Andreas Leathley

305 days ago
On 30.10.25 19:44, Larry Garfield wrote:
> The one outstanding question is whether we allow reordering using named arguments or preserve the underlying order. Arnaud says either one is doable. So far, only 2 people have commented on it (favoring reordering). We still want feedback from more people to see if there really is a consensus one way or another. (2 people is too small a sample size to draw any conclusions.)
To me reordering according to the named arguments seems more intuitive, because that is what you see when the PFA is created. The original order would seem surprising to me.

Tim Düsterhus

305 days ago
Hi Am 2025-10-30 19:44, schrieb Larry Garfield:
> The one outstanding question is whether we allow reordering using named > arguments or preserve the underlying order. Arnaud says either one is > doable. So far, only 2 people have commented on it (favoring > reordering). We still want feedback from more people to see if there > really is a consensus one way or another. (2 people is too small a > sample size to draw any conclusions.)
It appears there is a larger (and unanimous) agreement. This should be adjusted in the RFC then. Other than that, I've read through the RFC once more and have the following comments: 1. In the //// Regular functions //// examples: I believe the `static` is missing from every example. Given that the examples in the Overview at the start already have it, this might be a mistake? 2. // Placeholders may be named, too. Their order doesn't matter. This example will likely change given the reordering decision, but I'd like to note a typo: The 's' and 'i' parameters in the PFAs are missing their number. 3. (four(c: ?, d: 4, b: ?, a: 1))(2, 3); This is also a reordering example. Just listing it to make it easier for you to find. 4. Constant expressions I assume that “nested” PFA will just work? A little more complex example would be good, just to showcase what's possible. How about: public const BASE = 10; private \Closure $arrayToInt = \array_map(\intval(?, self::BASE), ?), My understanding is that this should be valid. 5. Implementation notes and optimizations // Transpiles into: The `static` should definitely be added here, since you are specifically talking about internal details. 6. Scoping Seeing the scoping section: Is it possible to partial a `parent::` call? I never tried with FCC. -------------------- All minor clarification bits. I'm super happy with the proposed semantics once the “reordering” question is resolved. Best regards Tim Düsterhus

Larry Garfield

304 days ago
On Fri, Oct 31, 2025, at 4:22 AM, Tim Düsterhus wrote:
> Hi > > Am 2025-10-30 19:44, schrieb Larry Garfield: >> The one outstanding question is whether we allow reordering using named >> arguments or preserve the underlying order. Arnaud says either one is >> doable. So far, only 2 people have commented on it (favoring >> reordering). We still want feedback from more people to see if there >> really is a consensus one way or another. (2 people is too small a >> sample size to draw any conclusions.) > > It appears there is a larger (and unanimous) agreement. This should be > adjusted in the RFC then. Other than that, I've read through the RFC > once more and have the following comments:
Mm, yeah, it would appear I have been outvoted. :-) So be it. The RFC has been updated to include reordering, and Arnaud will update the implementation shortly. I expect this to be the last major change, so we'll likely call the vote in a little over 2 weeks, baring any additional feedback.
> 1. In the //// Regular functions //// examples: > > I believe the `static` is missing from every example. Given that the > examples in the Overview at the start already have it, this might be a > mistake?
Artifact of how many times this RFC has been edited. :-) I think I got them all now.
> 2. // Placeholders may be named, too. Their order doesn't matter. > > This example will likely change given the reordering decision, but I'd > like to note a typo: The 's' and 'i' parameters in the PFAs are missing > their number.
I ended up removing this example and replacing it with another anyway.
> 3. (four(c: ?, d: 4, b: ?, a: 1))(2, 3); > > This is also a reordering example. Just listing it to make it easier for > you to find.
thumbs-up.gif
> 4. Constant expressions > > I assume that “nested” PFA will just work? A little more complex example > would be good, just to showcase what's possible. How about: > > public const BASE = 10; > > private \Closure $arrayToInt = \array_map(\intval(?, self::BASE), > ?), > > My understanding is that this should be valid.
Mine as well. I'll add that example, and if Arnaud corrects me we can deal with it then. :-)
> 5. Implementation notes and optimizations > > // Transpiles into: > > The `static` should definitely be added here, since you are specifically > talking about internal details. > > 6. Scoping > > Seeing the scoping section: Is it possible to partial a `parent::` call? > I never tried with FCC.
I... have no idea. Presumably? I'll add it and wait for Arnuad to yell at me if not. :-)
> All minor clarification bits. I'm super happy with the proposed > semantics once the “reordering” question is resolved.
Huzzah! --Larry Garfield

Tim Düsterhus

303 days ago
Hi I have just went through the RFC again (the 2025/11/01 17:54 version). On 11/1/25 18:54, Larry Garfield wrote:
>> 4. Constant expressions >> >> I assume that “nested” PFA will just work? A little more complex example >> would be good, just to showcase what's possible. How about: >> >> public const BASE = 10; >> >> private \Closure $arrayToInt = \array_map(\intval(?, self::BASE), >> ?), >> >> My understanding is that this should be valid. > > Mine as well. I'll add that example, and if Arnaud corrects me we can deal with it then. :-)
Small note: The example is missing the closing brace for the class. Other than that, I have no further notes, am happy with the RFC and plan to vote in favor. Best regards Tim Düsterhus

Ben Ramsey

303 days ago
> On Nov 1, 2025, at 12:56, Larry Garfield <larry@garfieldtech.com> wrote: > > On Fri, Oct 31, 2025, at 4:22 AM, Tim Düsterhus wrote: >> Hi >> >> Am 2025-10-30 19:44, schrieb Larry Garfield: >>> The one outstanding question is whether we allow reordering using named >>> arguments or preserve the underlying order. Arnaud says either one is >>> doable. So far, only 2 people have commented on it (favoring >>> reordering). We still want feedback from more people to see if there >>> really is a consensus one way or another. (2 people is too small a >>> sample size to draw any conclusions.) >> >> It appears there is a larger (and unanimous) agreement. This should be >> adjusted in the RFC then. Other than that, I've read through the RFC >> once more and have the following comments: > > Mm, yeah, it would appear I have been outvoted. :-) So be it. The RFC has been updated to include reordering, and Arnaud will update the implementation shortly.
Just another data point—I prefer reordering, too. It seems like the more intuitive experience. Cheers, Ben

Larry Garfield

288 days ago
On Sat, Jun 28, 2025, at 12:06 AM, Larry Garfield wrote:
> Hi folks. Arnaud and I would like to present take-2 at Partial > Function Application. > > https://wiki.php.net/rfc/partial_function_application_v2 > > It is largely similar to the previous PFA proposal from 2021, though > there are a number of changes. Most notably: > > * The implementation is simpler, because FCC already did part of the > work. This RFC can build on it. > * Constructors are not supported. > * But optional arguments and named placeholders are supported. > * It includes pipe-based optimizations. > > Note: We realize that this is a non-trivial RFC coming late in the > cycle. We are proposing it now because, well, it's ready now. If the > discussion goes smoothly, we're OK calling a vote on it for 8.5, > especially as it would complement pipes so well. If the discussion > runs longer, we're also OK with targeting 8.6 instead. We'll see how > that goes. > > <floor opens for discussion, Larry falls through the trap door>
It's been just over 2 weeks since the last comment in the thread, and the last change was made (allowing for parameter reordering). Please consider this the Intent to Vote post; I'll be calling the vote sometime probably late Tuesday or on Wednesday, baring any new issues before then. --Larry Garfield

Bob Weinand

288 days ago
Hey Larry, On 16.11.2025 21:51:59, Larry Garfield wrote:
> On Sat, Jun 28, 2025, at 12:06 AM, Larry Garfield wrote: >> Hi folks. Arnaud and I would like to present take-2 at Partial >> Function Application. >> >> https://wiki.php.net/rfc/partial_function_application_v2 >> >> It is largely similar to the previous PFA proposal from 2021, though >> there are a number of changes. Most notably: >> >> * The implementation is simpler, because FCC already did part of the >> work. This RFC can build on it. >> * Constructors are not supported. >> * But optional arguments and named placeholders are supported. >> * It includes pipe-based optimizations. >> >> Note: We realize that this is a non-trivial RFC coming late in the >> cycle. We are proposing it now because, well, it's ready now. If the >> discussion goes smoothly, we're OK calling a vote on it for 8.5, >> especially as it would complement pipes so well. If the discussion >> runs longer, we're also OK with targeting 8.6 instead. We'll see how >> that goes. >> >> <floor opens for discussion, Larry falls through the trap door> > It's been just over 2 weeks since the last comment in the thread, and the last change was made (allowing for parameter reordering). Please consider this the Intent to Vote post; I'll be calling the vote sometime probably late Tuesday or on Wednesday, baring any new issues before then. > > --Larry Garfield
I've just gave the RFC another deep read, and found only mentions of pass-by-ref. But not return-by-ref, and neither does the implementation handle this case: # ./sapi/cli/php -r 'function &r(&$a) { return $a; } $a = 1; $b = &r($a); ++$b; var_dump($a);' int(2) # ./sapi/cli/php -r 'function &r(&$a) { return $a; } $a = 1; $b = &r(?)($a); ++$b; var_dump($a);' Notice: Only variables should be assigned by reference in Command line code on line 1 int(1) Was this an oversight or intentionally omitted in the RFC? If it was, I'd like to hear the reasoning on this. Thank you, Bob

Larry Garfield

288 days ago
On Mon, Nov 17, 2025, at 10:26 AM, Bob Weinand wrote:
> Hey Larry, > > On 16.11.2025 21:51:59, Larry Garfield wrote: >> On Sat, Jun 28, 2025, at 12:06 AM, Larry Garfield wrote: >>> Hi folks. Arnaud and I would like to present take-2 at Partial >>> Function Application. >>> >>> https://wiki.php.net/rfc/partial_function_application_v2 >>> >>> It is largely similar to the previous PFA proposal from 2021, though >>> there are a number of changes. Most notably: >>> >>> * The implementation is simpler, because FCC already did part of the >>> work. This RFC can build on it. >>> * Constructors are not supported. >>> * But optional arguments and named placeholders are supported. >>> * It includes pipe-based optimizations. >>> >>> Note: We realize that this is a non-trivial RFC coming late in the >>> cycle. We are proposing it now because, well, it's ready now. If the >>> discussion goes smoothly, we're OK calling a vote on it for 8.5, >>> especially as it would complement pipes so well. If the discussion >>> runs longer, we're also OK with targeting 8.6 instead. We'll see how >>> that goes. >>> >>> <floor opens for discussion, Larry falls through the trap door> >> It's been just over 2 weeks since the last comment in the thread, and the last change was made (allowing for parameter reordering). Please consider this the Intent to Vote post; I'll be calling the vote sometime probably late Tuesday or on Wednesday, baring any new issues before then. >> >> --Larry Garfield > > I've just gave the RFC another deep read, and found only mentions of > pass-by-ref. But not return-by-ref, and neither does the implementation > handle this case: > > # ./sapi/cli/php -r 'function &r(&$a) { return $a; } $a = 1; $b = > &r($a); ++$b; var_dump($a);' > int(2) > # ./sapi/cli/php -r 'function &r(&$a) { return $a; } $a = 1; $b = > &r(?)($a); ++$b; var_dump($a);' > > Notice: Only variables should be assigned by reference in Command line > code on line 1 > int(1) > > Was this an oversight or intentionally omitted in the RFC? If it was, > I'd like to hear the reasoning on this.
Just an oversight. We both use return-by-ref so rarely we forgot it's even a thing. :-) I've updated the RFC to include it, and Arnaud said it's easy to update the implementation so will do so shortly. Thanks for the catch. As this is largely just a forgotten case (I just added a "/return-by-ref" to the text), I'm not sure if it needs a cooldown reset. For the moment I will plan to continue opening the vote in the next few days, but if someone requests it I'll mark it a minor change and delay a week. --Larry Garfield