# [RFC] Partial Function Application v2

- Thread: https://externals.io/message/127781
- Original archive: https://news-web.php.net/php.internals/127781
- Started by Larry Garfield on 2025-06-28T05:06:52+00:00
- Messages: 53
- RFC: Partial Function Application, v2 (Implemented (PHP 8.6)) — https://externals.io/rfc/partial_function_application_v2.md
- Page 1 of 3: messages 1 to 25 (25 per page, add `?page=N` to the URL)
- Next page: https://externals.io/message/127781.md?page=2
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #127781 — Larry Garfield — 2025-06-28T05:06:52+00:00

URL: https://externals.io/message/127781#127781

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

---

## #127782 — Stephen Reay — 2025-06-28T09:46:46+00:00

URL: https://externals.io/message/127781#127782 · In reply to: #127781

> 
> 
> ﻿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

---

## #127783 — Eugene Sidelnyk — 2025-06-28T10:44:24+00:00

URL: https://externals.io/message/127781#127783 · In reply to: #127781

> 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 🙂

---

## #127784 — Levi Morrison — 2025-06-29T07:13:29+00:00

URL: https://externals.io/message/127781#127784 · In reply to: #127781

>
> 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.

---

## #127788 — Ben Ramsey — 2025-06-30T00:29:19+00:00

URL: https://externals.io/message/127781#127788 · In reply to: #127781

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

---

## #127793 — tim@bastelstu.be — 2025-06-30T07:28:52+00:00

URL: https://externals.io/message/127781#127793 · In reply to: #127781

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

---

## #127801 — Larry Garfield — 2025-06-30T14:55:36+00:00

URL: https://externals.io/message/127781#127801 · In reply to: #127788

> 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

---

## #127804 — Larry Garfield — 2025-06-30T15:12:54+00:00

URL: https://externals.io/message/127781#127804 · In reply to: #127793

> 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

---

## #127835 — tim@bastelstu.be — 2025-07-02T15:04:34+00:00

URL: https://externals.io/message/127781#127835 · In reply to: #127781

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

---

## #127842 — Arnaud Le Blanc — 2025-07-02T16:23:22+00:00

URL: https://externals.io/message/127781#127842 · In reply to: #127835

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

---

## #127846 — Gina P. Banyard — 2025-07-02T19:07:03+00:00

URL: https://externals.io/message/127781#127846 · In reply to: #127842

> 
> > […]
> 
> 
> 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

---

## #127890 — Rob Landers — 2025-07-04T22:27:34+00:00

URL: https://externals.io/message/127781#127890 · In reply to: #127781

> 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

---

## #127934 — Larry Garfield — 2025-07-07T15:35:09+00:00

URL: https://externals.io/message/127781#127934 · In reply to: #127890

> 
> > […]
>
> 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

---

## #127946 — Levi Morrison — 2025-07-07T17:30:27+00:00

URL: https://externals.io/message/127781#127946 · In reply to: #127890

> 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.

---

## #127995 — tim@bastelstu.be — 2025-07-10T13:31:32+00:00

URL: https://externals.io/message/127781#127995 · In reply to: #127842

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:
> > […]
> 
> 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.

> > […]
> 
> The underlying function name (like FCCs)

See above for my `var_dump()` comments.

> > […]
> 
> 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

---

## #128182 — Larry Garfield — 2025-07-22T20:02:22+00:00

URL: https://externals.io/message/127781#128182 · In reply to: #127781

> 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

---

## #128183 — tim@bastelstu.be — 2025-07-22T20:18:57+00:00

URL: https://externals.io/message/127781#128183 · In reply to: #128182

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

---

## #128187 — tim@bastelstu.be — 2025-07-23T08:56:42+00:00

URL: https://externals.io/message/127781#128187 · In reply to: #127781

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

---

## #128189 — tim@bastelstu.be — 2025-07-23T12:49:44+00:00

URL: https://externals.io/message/127781#128189 · In reply to: #128182

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

---

## #128191 — Larry Garfield — 2025-07-23T13:55:41+00:00

URL: https://externals.io/message/127781#128191 · In reply to: #128187

> Hi
>
> Am 2025-06-28 07:06, schrieb Larry Garfield:
> > […]
>
> I've now given the RFC an in-depth read. I have the following remarks:
>
> 1.
>
> > […]
>
> 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:
>
> > […]
>
>  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:
>
> > […]
>
> 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

---

## #128192 — Larry Garfield — 2025-07-23T13:57:13+00:00

URL: https://externals.io/message/127781#128192 · In reply to: #128183

> Hi
>
> Am 2025-07-22 22:02, schrieb Larry Garfield:
> > […]
>
> 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

---

## #128206 — tim@bastelstu.be — 2025-07-24T09:54:42+00:00

URL: https://externals.io/message/127781#128206 · In reply to: #128192

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

---

## #128207 — tim@bastelstu.be — 2025-07-24T10:03:47+00:00

URL: https://externals.io/message/127781#128207 · In reply to: #128191

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.

> > […]
> 
> 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

---

## #128208 — tim@bastelstu.be — 2025-07-24T11:52:14+00:00

URL: https://externals.io/message/127781#128208 · In reply to: #128207

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

---

## #128213 — Larry Garfield — 2025-07-24T13:47:06+00:00

URL: https://externals.io/message/127781#128213 · In reply to: #128206

> Hi
>
> Am 2025-07-23 15:57, schrieb Larry Garfield:
> > […]
>
> 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

---

Next page: https://externals.io/message/127781.md?page=2
