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

Reply via email to