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 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
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 🙂
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.
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
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.
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
Hi
Am 2025-06-28 07:06, schrieb Larry Garfield:
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
Hi
Am 2025-06-28 07:06, schrieb Larry Garfield:
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
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.
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
Hi Tim,
We will update the RFC, but here are a few answers:
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 tovar_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
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
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 tovar_dump()
, Reflection, and to observers?PFAs are instances of the Closure class (like FCCs), and will look
like a Closure tovar_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 thevar_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
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
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.comHi 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
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.
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
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