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]