Hey Rowan,

> Am 07.05.2026 um 12:52 schrieb Rowan Tommins [IMSoP] <[email protected]>:
> 
> On 6 May 2026 21:09:45 BST, Bob Weinand <[email protected]> wrote:
>> Volker and I drafted a RFC:
>> 
>> https://wiki.php.net/rfc/scope-functions
> 
> 
> Hi Bob, 
> 
> An I right in thinking that this is essentially equivalent to "automatic 
> capture by closure", with an extra restriction on the lifetime of the 
> resulting closure? It would be good to spell out in the RFC how this compares 
> to other approaches, and other languages.

I think the closest thing to what we propose here is GCC Nested Functions: 
https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html
They have similar lifetime semantics - cannot outlive their declaration scope 
and cannot be redeclared either (by design because they're named, but scoped 
function names like that are not a thing in PHP, hence we assign to a variable).

> In particular, JavaScript's capture semantics are somewhat notorious as a 
> source of confusion. A common example is creating closures in a loop which 
> all use the same variable; as far as I can see, the proposed semantics would 
> allow this as long as they were executed before the parent scope ends:
> 
> 
> ```
> function parent_scope() {
>    $values = range(1,10);
> 
>    $scoped_functions = [];
>    foreach( $values as $v ) {
>        $scoped_functions[] = fn($v) {
>            echo "Chosen item is {$v}";
>        };
>    }
> 
>    run_random_callable($scoped_functions);
> }
> 
> /**
> * @param callable[] $callables
> */
> function run_random_callable($callables) {
>    $pick = array_rand($callables);
>    $callables[$pick]();
> }
> 
> // Will always output 'Chosen item is 10'!
> // The list of "scoped functions" will all actually refer to the same $v, 
> updated in the loop
> parent_scope();
> ```
> 
> 
> I don't know if there's some way to make the lifetime even more restricted, 
> so that the use cases of "immediate execution" are allowed, but this kind of 
> code is not.

Have you missed this following restriction and example in the RFC?

> Re-evaluation: The same scope function declaration (same source code 
> location) invalidates the previous instance. Should it still be called, an 
> error is raised.
> 
> Any scope closure is only allocated a single slot, preventing recursion and 
> double declaration. There are two choices: guaranteeing uniqueness (returning 
> the active Closure again) or invalidating the previous Closure. We chose the 
> second option to avoid the misconception that scope functions would capture 
> the current variables. E.g. in foreach loops, it will always print the 
> current active value and never the value at declaration time.
> 
> $closures = [];
> for ($i = 0; $i < 3; $i++) {
>     $closures[] = fn() { return $i; };
> }
> var_dump($closures[2]()); // int(3)
>  
> $closures[0](); // Error: Cannot call scope function: defining scope has 
> exited

This restriction exists exactly because of that issue.


> For something like "run this in a transaction", the closure is really acting 
> like a poor man's "continuation": it creates two new stack frames 
> (transaction wrapper, callback) when what you really want is to *interleave* 
> the boilerplate and the case-specific code.

One thing which this does is interleaving with a well-defined boundary. return 
works and does not cross the whole function, just the callback.
You don't get a sudden goto or break skipping some of your code. The internal 
variables of the wrapper frame are isolated and don't leak to you. Argument 
values to the wrapper are properly type checked.

> In that sense, hygienic macros are probably the "ideal" solution - everything 
> is expanded inline into a single scope, and there's no Closure object which 
> can be misused.

With "hygienic" you mean the macro has to produce full expressions, right, i.e. 
not C preprocessor style.

> Context Managers could in fact be implemented as such a macro, and as I 
> understand it, the implementation is basically doing that internally by 
> manipulating ASTs.
> 
> I wonder if there's any "minimal" macro system which would allow this kind of 
> inline boilerplate expansion, without needing to design an entire 
> meta-language?

You wonder rightly, but the main problem is the nature of the 
language/implementation. How would you compile that?
op_arrays are fixed at compile time and not going to change afterwards.

So, you can either try calling functions at compile time to manipulate a 
functions code (let's say e.g. all !-suffixed function names get called at 
compile time with some sort of token stream or AST, whatever) or invent very 
clever hacks to somehow generate op_array snippets on the fly at runtime.
That once-per-request nature of PHP also makes it very difficult to efficiently 
cache such code (unless you assume every invocation of that function will 
always output the same, which you cannot even rely on, because every request 
could invoke a different file defining the same macro etc.).

You will also get interesting behaviours with jumps, e.g. a goto will suddenly 
... jump over a bunch of logic, so any macro will have to do try/finally. while 
loops inside that macros, do they count towards the break/continue stack etc.?

It's a big rabbit hole. You'll find that it's not impossible to do, but it has 
quite a few trade offs and possibly odd choices to make.

I am not opposed at all to such a consistent macro system though.
... but I would not make a transaction like that a macro. For inlining 
variables in a html or sql snippet with proper escaping, definitely great.
To me, macros should meaningfully transform their contained code rather than 
wrapping a bit of logic around, which would be covered by this RFC.
It's complimentary, not a replacement.

Thanks,
Bob

Reply via email to