Hi
Am 2025-11-29 20:12, schrieb Rowan Tommins [IMSoP]:
I think the reason comparing to other languages is so important relates
to what Steve Klabnik calls "the Language Strangeness Budget":
https://steveklabnik.com/writing/the-language-strangeness-budget/
Anything that is going to surprise users coming from other languages
carries a cost which we need to justify. Generally, that means either
a) the expected way wouldn't work in PHP for some reason; or b) we
think we can do better by learning from the problems of other
languages.
In basically every language derived from ALGOL and not from Pascal,
variable declarations take the form of a statement inside a block, so
doing something different definitely costs us some Strangeness Budget.
I generally agree that it is important to not needlessly invent new
stuff folks need to learn and I'm trying hard in the design of my RFCs
and the discussion of other RFCs to figure out how to simplify things or
make them compose better. Syntax that is unfamiliar to users coming from
other languages is bad, but that shouldn't come at the expense of users
that are already familiar with PHP or the internal consistency of PHP.
I'd also argue that the proposed `let()` block syntax is intuitive to
understand when seeing it when familiar with block scoping, so it has
only a small impact on the strangeness cost.
FWIW: The proposed `let()` block is not too dissimilar from the `let
<var> = <expr> in <expr>` syntax available in ML-style languages such as
OCaml or Haskell and also Nix.
You've tried to make the case that PHP has unique challenges with
variable declarations, but I'm not convinced.
In particular, I don't think this statement is accurate:
Other languages with block scoping, particularly statically typed
languages, avoid this ambiguity by requiring all variables to be
explicitly declared
[…]
Every such language has to answer the same question: does "my_var" at
line B refer to the block-scoped variable declared below it at line C?
That sentence you quoted was specifically in the context of the initial
paragraph of that section, contrasting PHP - where block scoping is
expected to be used comparatively sparingly - against languages where
variable declarations are a more “bread and butter” part of the
development process, because formally / explicitly declaring variables
is a necessity for one reason or another.
The only way to *avoid* the ambiguity is to forbid all statements
between the start of the scope and a declaration - that is, raise an
error even if line B doesn't reference "my_var". Notably, if you keep
the ALGOL-style declarations, you can start with this rule, and then
relax it later, as happened with C99.
So if it's not because we can't implement ALGOL-style declarations, is
there something we think we can do better than them?
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;
}
because there is a much less direct / less rigid relationship between
the individual `let` statements, leaving room for interpretation of
“what is considered a statement”. As an example, is a goto jump label a
statement?
{
let $foo = 1;
label:
let $bar = $foo++;
goto label;
}
Forcing all the declarations into a single statement would resolve that
ambiguity, but I feel like that those restriction would feel arbitrary
and have a strangeness cost without any of associated benefits that the
`let()` block has.
As far as I can see, any proposed statement of the form "let($foo) {
... }" is directly equivalent to ALGOL-style "{ let $foo; ... }"
Yes, that is my understanding.
The unique innovation appears to be when using it with a single
statement rather than a block, such as in this example from the RFC:
let ($user = $repository->find(1)) if ($user !== null) { ... }
With ALGOL-style declarations, that requires an extra pair of braces:
{ let $user = $repository->find(1); if ($user !== null) { ... } }
[…]
It's an interesting feature, but whether it's worth the cost in
"strangeness", I'm not sure.
Being able to declare variables with “if” lifetime that I can also check
is a big part of the benefits of the proposed syntax and something I'm
missing in other languages. C++ as a language in the “PHP syntax family”
added it in C++17 with the following syntax (taken from
https://en.cppreference.com/w/cpp/language/if.html):
if (char buf[10]; std::fgets(buf, 10, stdin))
if (std::lock_guard lock(mx); shared_flag)
Translated to PHP this would be:
if (let $user = $repository->find(1); $user !== null) { }
which would somewhat match the syntax of a `for()` loop with the
semicolon. But then the more composable
let ($user = $repository->find(1)) if ($user !== null) { }
would not be so different syntax-wise and would not require adding the
grammar to each and every control structure.
On the other hand, I note that the "process_file" example in the RFC
can't make use of the single-statement form: "let ( $lock =
$file->lock(LockType::Shared) ) try { ... }" would be legal, but
wouldn't release the lock until after the catch block.
As discussed in the sibling thread, allowing a single statement on `try`
should be possible (if necessary with a special case for `let`):
https://news-web.php.net/php.internals/129582
Best regards
Tim Düsterhus