Hi someniatko

I think you have a firm grasp of the key issues but I don't agree with
your conclusion.

> Problem no. 2 could be addressed by
> allowing "complex" expressions consisting of, potentially, few
> statements, language-wide, solving the issue both for short closures
> and for `match`

I have analysed this approach a while back and I don't think there is
a universal and elegant solution. I have mentioned this in the list
but never fully explained it.

Upfront, when I say "block expression" I'm talking about a block `{}`
that contains any number of statements with some terminating
expression that will be returned.

$x = {
    foo();
    bar();
    <= baz();
};

// Result of baz() is now assigned to $x

There are three potential use cases for language wide block expressions.

1. Match expressions
2. Arrow functions
3. Everything else

The problem is that they all have slightly different semantics.

1. A block expression in a match arm would require a return value
depending if the outer match return value is used

match ($x) {
    1 => {}, // Doesn't require a return value
}

$y = match ($x) {
    1 => {}, // Error, this does require a return value
};

2. Arrow functions would only require a return value if the function
has an explicit return type (to be consistent with functions and
normal closures)

$x = fn() => {}; // This is fine, the function returns null
$x = fn(): ?int => {}; // Uncaught TypeError: Return value of
{closure}() must be of the type int or null, none returned

It's very questionable whether we even want to allow block-style
return values in arrow functions.

$x = fn() => {
    foo();
    bar();
    <= baz(); // Why should we allow this? You can just use return
};

3. For every other expression the return value of the block would
always be required

// All of these are errors, return value is required
$x = {};
foo({});
{} + 1;
// etc.

It's also highly questionable whether use case 3 is actually very
useful at all because PHP doesn't have block scoping and all the inner
variables will leak out into the outer scope. The only potential
improvement here is readability.

$this->foo = {
    $bar = new Bar();
    $foo = new Foo();
    $foo->bar = $bar;
    <= $foo; // Or whatever block syntax
};

// $bar still exists here but it's a little
// more obvious it shouldn't be used anymore

No matter if statement blocks become a language wide feature or not,
we won't get around handling these cases slightly differently.

An additional complication is that blocks already exist as "statement
list" statements:
https://github.com/php/php-src/blob/php-7.4.5/Zend/zend_language_parser.y#L427

function foo() {
    {
        // This is a "statement list" statement
    }

    if (true) {
        // This is also a "statement list" statement
    }
}

Whether we'd also (a) convert these to block expressions or (b) keep
"statement list" statements and block expressions separate is unclear.
If we do convert "statement list" statements to block expressions
we'll have to make the semicolon of a statement level block expression
optional to avoid BC breaks. I received a lot of criticism for the
same thing in this RFC. If we don't convert "statement list"
statements to block expressions empty blocks won't become valid syntax
in match arms and we'll have to explicitly allow them in the grammar
(which is what this RFC is doing right now).

(a)

{
    // Blocks are expressions now, expressions at a statement level
require a semicolon.
    // The semicolon must stay optional or we'll have a BC break.
}

(b)

{
    // This is still a statement
}

match ($x) {
    1 => {
        // Also a statement which means it can't be used here unless
we explicitly allow expressions AND "statement list" statements
    },
}

To summarize, blocks are only really useful in match arms and arrow
functions and behave differently even in just these two cases. While I
wouldn't mind language wide blocks it isn't the universally elegant
solution people make it up to be.

This is the best explanation I could give. Let me know if it's still
not completely clear.

> IMO this covers vast majority of use-cases of the PHP statements.

Not really, if you look at Nikita's analysis of 50 random switch
statements. It only covers ~40%.

> IMO a good language should enforce better quality.

The main issue I have with this is that good code quality doesn't look
the same to everybody. Even programmers with decades of experience
will disagree on fundamentals. I'd be very hesitant to say that moving
two lines into a function is an improvement, especially if it's only
called once. The only thing it does is disrupt the reading flow.

match ($x) {
    1 => {
        foo();
        bar();
    },
}

vs.

match ($x) {
    1 => fooAndBar(),
};

function fooAndBar() {
    foo();
    bar();
}

I don't think using a closure for all of these cases is a viable solution.

> Also, those people who would benefit
> from less boilerplate which match in expression-only form provides,
> are usually the people who care about code quality.

It's not that black and white. I work in a lot of legacy projects that
could benefit from match expressions but it's simply not realistic to
refactor every single switch statement that contains more than a one
liner. Also, I don't always care about code quality the same. If I
write a throwaway script I wouldn't care if the match arm contains 20
lines but the safety of the match would be useful nonetheless.

Ilija

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to