IIFE Statements & Expressions

One of the primary goals of pattern matching is to replace switch, and any syntactic overlap will make education and documentation (and googling) less clear, so reusing switch isn't viable. As for two-word keywords, you're correct, but on match wouldn't be clearer than match imo, and since the vast majority of JS is written using One True Brace Style, virtually nobody will run across the whitespace "issue".

The suggestion for switch once was intended to fix the existing switch-statement falling through. Pattern matching would not reuse the switch keyword. It would have its own two-word prefix, like on match.

To be clear though, I still think it's preferable to reuse existing keywords (or use a reserved word) wherever possible.

There is precedence for JavaScript depending on newlines. Take return, for example. These two mean different things:

return { x: 2 };

return
{ x: 2 };

The first returns an object. The second returns nothing, and on the next line, a block scope starts, that has a 2 statement labeled by x.

I'll also point out that, in the case of "on match", you're also depending on a new line not existing. For "on match" to work, a new line can not be between those two words, otherwise, it might get interpreted as something else. Granted, no one in practice would try to split up a two-word operator over multiple lines. But, it's still worth pointing out that this is just moving the problem to a less-common location, not eliminating it.

I don't think this (making match(){ a header) is the same, but I'll let other people weigh in. It's not entirely objective.

Has this been considered?

case (v) { ... }

I may be mistaken, but I believe the original pattern-matching proposal actually used that syntax (maybe?). But, that runs into @ljharb's restriction that there shouldn't be any syntax overlap with switch - using "case" would be included with that.

Fair enough. I can understand not wanting the pattern-matching grammar to imply something switch related. Using case seems distinct enough to me personally, but if it's been discussed already, I'll just let it be. It wouldn't be the first new syntax that I didn't really like.

@ljharb - You could possibly use short or final (both reserved words) a bit like return and yield, but to short-circuit evaluation of a do-expression, and explicitly define the value that it evaluates to:

// let `x` be `undefined` if `predicate` is truthy, and `3` otherwise
let x = do {
    if (predicate) short;
    let x = 1, y = 2;
    short x + y;
}

This would dodge the issue of implicit evaluation altogether.

You're using short like return or yield... That is one of the most beautifully integrating solutions I've seen. It looks so much like break. But in that case, what if instead we extend break's syntax with structuring / destructuring declarations and expressions?

let [ a ] = do { break [ 11 ] } //a === 11

let b = do { break ( 22 ) } //b === 12

let { x } = while( true ) {
    ...
    let x = i + j;
    break { x };
}

let y, z;

[ y, z ] = for( ... ) {
    ...
    break [ 22, 33 ]
}

let w, s;

[ w, s ] =
    outerlabel:
    while( true ) {
        ...
        while( true ) {
            ...
            break outerlabel [ 44, 55 ]
        }
    }

By requiring declaration or an expression, break's syntax remains backward compatible with label identifiers. Now, do blocks only need to drop the while predicate:

const [ x ] = do {
    ...
    break [ 55 ];
}

And with one of the proposed matching syntaxes:

const result = match( something ) {
    when ( 2 ): do {
        for( ... ) { ... }
        break ( f( ... ) )
    }
    ...
}

@0X-JonMichaelGalindo - though, what does "break myVariable;" do? Does it "give back" the value stored in "myVariable", or does it go to the "myVariable;" label?

It jumps to the label, exclusively and always, like I wrote and repeatedly emphasized in my post.
By requiring a declaration [ ... ] or { ... }, or an expression ( ... ) we keep compatibility with label syntax. (That was kind of the whole point of my post... But I guess I wasn't clear, sorry.)

When you say that it allows any expression, do those expressions always have to be wrapped in parentheses (with the exception of declarations, which seem to be allowed to be outside of parentheses)?

Perhaps I was understanding those parentheses to be optional (i.e. they were just part of the expression, not required by the syntax)

Sorry. I just double-checked, and I think short and final were dropped from the reserved words list, so they're now valid variable names.

It may still be possible to reserve short, as it's unlikely to be used as a variable in practice, but it's not a simple as when it was reserved for future use.

I didn't really understand how overloading break would work. The break keyword jumps to the end of the innermost (or labelled) loop. It doesn't just exit the do-expression. Loops do not evaluate to anything, so unless I'm missing something??.

In fact, I'd expect break[<expression>] to break to the label named by <expression>, or maybe the nth loop if it's a number.

Besides, if we had short, we could just do this:

let a = do { short 11 } // a === 11

Short is not a reserved word, so this wouldn't work. But I still really like the general idea.

Right now, blocks don't evaluate to anything, which is inconvenient. It's why we have to use IIFEs. That's what these proposals are trying to fix. One problem with a stand-alone do { } block is if you want to break out of a loop and evaluate to a value... you can't. :-/ Just like IIFEs.

Trying to read up on the syntax now and state it clearer, hang on...
Okay! It's called "grouping" here, not an expression. ( ... ) is the "grouping" operator.
We extend break with "declaration" and "grouping" syntax. (I think I'm saying that right, hopefully.)

break label
break ( identifier | expression )
break label ( identifier | expression )
break [ identifier ]
break label [ identifier ]
break { identifier }
break label { identifier }
break label ( [ identifier ] )
break label ( { identifier } )
1 Like

I don't really understand what's being proposed here.

There's currently no need to break out of a loop and evaluate to a value, as loops cannot evaluate to anything. Is this syntax intended to permit loop-expressions (which would be a form of comprehension, which was already (sadly) rejected)?

Technically (if I understand and remember correctly), you can break out of an if-block (but not continue), just like a switch-block, and the proposed match-expression, so breaking with a value would be useful there.

Break would need to support the optional label it supports now, and a new optional expression. This would need to be an arbitrary expression (and value) though, not just an identifier. The problem is that an identifier is also a valid expression, so we can never make break <expression> valid grammar.

There are perfectly good alternatives in isolation (for example, break with <expression> or break as <expression>), but we cannot create a grammar (using break or continue) that would compliment return <expression> or yield <expression>, without introducing an inconsistency into the language.

It's not totally out of the question. You'd need to establish that nothing major would break, which some larger organizations are able to do.

We could possibly make short a reserved word (again) inside do-blocks, and only deprecated (as a variable name) otherwise, and add it to the list of variables you can use in some contexts, but should avoid using altogether in practice.

But... heh... personally I use it all the time with typed arrays... I kind of jumped onto that train super excited the second browsers started allowing char / float / int etc.

I don't think I specifically use "final" for anything, but I might have code out there that uses it... But more importantly I still have a problem with the do block:

let x;

looping:
while( true ) {
    //...
    x = do {
        //...
        //what if I want to break out of while{} here?
        break looping; //now x is undefined. :-(
        //breaking out of do{} is just as inconvenient as IIFE.
    }
}

Of course, I still want do{} so I can use blocks anywhere expressions are required.

if( x && do { ... } ) ...

That use of short is a break that can't use labels. It forces a choice: 1) either break out of any block structure but return nothing, or 2) break out of only the current block and return something.
I just feel like it would make more sense to have both, right? Let's break out of any block structure, and also return something.

we cannot create a grammar (using break or continue) that would compliment return <expression> or yield <expression>, without introducing an inconsistency into the language.

There's also throw <expression>.

Here's an inconsistency:

let x = yield;

There's no "compliment" for return.

Here's another inconsistency:

yield* someOtherIterator;

Here's another inconsistency:

yield something;
console.log( "This can still execute. With return, it could not." );

Here's another inconsistency:

break label;

return, throw, and yield have no equivalent, because they are not break.

These are 4 different keywords with 4 fundamentally different, non-comparable uses. But you are correct, this syntax does not compliment the other 3.
This syntax for break is unique: a combination of a logic-jump and an evaluation. It is more like call than like those 3, but it is definitely not call.

That's my conclusion, and I thought about this very carefully before posting. It was definitely worth considering. (And edited this post a dozen times.)

You also mentioned the identifier vs. expression thing. This is just bad communication on my part, so please bear with me. (I'll also update that post.)

Break would need to support the optional label it supports now, and a new optional expression. This would need to be an arbitrary expression (and value) though, not just an identifier.

An arbitrary expression using grouping syntax. That's what I'm trying to propose, and it is fully compatible with current grammar.

break ( 5 + 5 ); //SyntaxError: unexpected token: '('

becomes:

let x = while( ... ) {
    break ( 5 + 5 );
}
//x === 10

Also, I should mention (maybe?), this isn't really "my" idea, it's block, br from WASM. br has a label as a byte immediate (defaults to breaking out of the current block), and optionally returned types, unambiguously different from the label, since they're on the stack. This syntax is my idea though. Not sure how else you'd do it.

@0X-JonMichaelGalindo - Thanks for taking the time to explain. I misunderstood a few things from your original post, so appreciate your patience.

I see the need for breaking-with-a-value now, and was convinced by your rationale for the non-complimentary syntax too. You're right. I just wasn't getting it.

I'm still not keen on requiring parens in that context though, though I only have subjective reasons, to be honest. I'd just personally prefer a keyword. Something like:

break [<label>] [as <expression>]

Take these examples, using your proposed syntax:

break (5 + 5);
break looping (5 + 5);

Would you not prefer something along these lines?

break as 5 + 5;
break looping as 5 + 5;

I can see the logic in your proposal. I just don't really like how it looks in practice.


I'd be grateful if someone could explain why we can't just overload the block-headers (if, while, switch etc) to function as operators. This style of grammar would be really nice to have, if it was possible:

let x = while ( ... ) { ... }

EDIT: When I say "just overload the block-headers", I don't mean to trivialize the work that goes into standardizing, implementing and maintaining these proposals. I'm just bikeshedding.

UPDATE: I double-checked the break-inside-an-if-block thing, and it's only applicable when the if-block is labelled (else commonplace stuff like if (done) break inside a loop would not work).

The idea of simply statements into expressions is discussed here on the do expressions proposal. There's a lot of technical hurdles that need to be overcome, that make this more challenging than one might think. You'll see a lot of different issues brought up in that thread - sometimes they found work-arounds, but there's also some kinks that weren't fully resolved.

If they go the route of turning statements into expressions, I think the concept of being able to "break" with a value would flow nicely into it (otherwise, I'm not really sure what an expression-for-loop or expression-while-loop would do - it's not something that really got discussed in that thread, they mostly focused on if/else).

1 Like