On 17/12/2025 19:09, Tim Düsterhus wrote:
The difference I'm seeing is that for languages where variable declarations (and block scoping) are a core part of the language, the scoping rules are “moulding” (if that word makes sense here) how code in that language is written and how folks reason about the code. This is different for a language where block scoping is added after-the-fact and remains an optional part of the language.


I can sort of see where you're coming from, but counter-examples include Perl and JS, both of which treat declarations as optional, and innovated new variants of them, but stuck to the "ALGOL-style" syntax.


I feel that the C99 requirements and syntax would still have more ambiguity compared to the proposed `let()` syntax in cases like this:

    {
        let $foo = bar($baz); // What is $baz referring to? Particularly if it is a by-reference out parameter.

        let $baz = 1;
    }


As discussed on the other thread, this part of the discussion turns out be moot, because exactly the same ambiguity exists in the proposed syntax:

let($foo = bar($baz), $baz=1) { ... }

The syntax alone doesn't tell you what that will do, only knowing the choices made in the RFC.



PHP already limits where "goto" can jump to; I don't know how that's implemented, but I don't think we need to get into philosophical definitions to say "you can't jump into the middle of a declaration list".

Another, perhaps better, example that is not handled well by any C-derived language that we are aware of is block scoping in combination with `switch()`


Compile Error: 'let' declarations whose closest block is a 'switch' are forbidden.

Problem solved.



Other languages have other ecosystems and other user expectations. PHP has extensive “scope introspection” functionality by means of `extract()`, `compact()`, `get_defined_vars()` and variable variables. Folks are used to being able to access arbitrary variables (it's just a Warning, not an Error to access undefined variables) and there's also constructs like `isset()` that can act on plain old local-scope variables. Adding semantics like the “temporal dead zone” from JavaScript that you suggested in the other thread would mean that we would need to have entirely new semantics and interactions with various existing language features that folks already know, adding to the complexity of the language.


I don't think most of these would need special semantics at all. If it's an error to read from $foo, it follows that it's an error to read from $$x when $x is 'foo', and an error to run compact('foo').

It seems equally obvious to me that get_defined_vars() would omit the variable, and isset() would return false.

There might be some nuances to the implementation, but I imagine it would be similar to uninitialized object properties: a sentinel value in the zval, and checks for that sentinel in suitable places.


I don't know JS well enough to name them, but I bet the ECMA committee had to consider similar features, and most users simply never encounter them.



For me this works, because the `let()` is preparing me that “this code is doing user processing” and the `if()` is just an “implementation detail” / “means to an end” of that. By the block scoping semantics I know that when I read the closing brace, the user processing is finished. The function is a <h1>, the user processing is a <h2> and the `if()` is a <h3> if that analogy makes sense. If I just want to get an overview over the function, I only care about the <h2> headings.


I can't think of any situation where "this block of code contains a scoped variable" is more important information than "this block of code might not run at all".

In the analogy, I would always class an if() as an h2; it's one of the most fundamental pieces of control flow.


I understand that some languages have postfix conditions, but being able to place an `if()` after another control structure is not a new thing. The same would apply to:

    foreach ($users as $user) if ($user->isAdmin()) {
        echo "User is admin";
    }

which is already valid PHP.


That syntax would never occur to me, and if I saw it, I'd immediately add the "missing" braces.



The comma would leave ambiguity in cases like `if (let $repository = $container->getRepository(), $user = $repository->find(1))`.


I think you missed the context: in Perl, the comma isn't part of the "if" or "let", it's just a general purpose operator, so this ambiguity is just the precedence of "," vs "my".


That's probably why C++ uses the `;` as a delimiter there.


But sure, that works too. I was just exploring possibilities.





I think, in a nutshell, this is where we have opposing opinions:


we believe the “top of the block” semantics are important for block scoping to work well in PHP due to its unique semantics and 30y history.


I believe using familiar declaration syntax is important for block scoping to work well in PHP due to its strong similarity to other languages in the ALGOL-C-Java family, and the 65 year history of that family.


--
Rowan Tommins
[IMSoP]

Reply via email to