The current proposal makes pattern matching based on anything after the -> being a statement, and the whole case construct is a statement. The motivation for this limitation is largely to avoid the previous iteration's use of blocks in statement context, which would raise all the questions of do expressions (most controversially, the use of completion values).
I think we could still support match expressions without involving completion values. The idea here would be to make the thing after -> have a similar grammar to the thing after the =>: It can either be a block or an expression. If all when clauses in a case construct are expressions, then the whole thing can be used as an expression. Object literals on the right hand side of -> need to be parenthized, as with =>.
From the README which work well as expressions:
function todoApp (state = initialState, action) {
return case (action) {
when {type: 'set-visibility-filter', filter: visFilter} ->
({...state, visFilter})
when {type: 'add-todo', text} ->
({...state, todos: [...state.todos, {text}]})
when {type: 'toggle-todo', index} -> ({
...state,
todos: state.todos.map((todo, idx) => idx === index
? {...todo, done: !todo.done}
: todo
)
})
when {} -> ({}) // ignore unknown actions
}
}
// The following just works now, whereas it previously required some kind of extension
<Fetch url={API_URL}>{
props => case (props) {
when {loading} -> <Loading />
when {error} -> <Error error={error} />
when {data} -> <Page data={data} />
when _ -> throw new Error('badmatch')
}
}
</Fetch>
const getLength = vector => case (vector) {
when { x, y, z } -> Math.sqrt(x ** 2 + y ** 2 + z ** 2)
when { x, y } -> Math.sqrt(x ** 2 + y ** 2)
when [...etc] -> vector.length
};
getLength({x: 1, y: 2, z: 3}) // 3.74165
(Sorry, some of the following examples are pretty stupid)
As a statement, case would permit a mixture of blocks and expressions (why not?):
case (x) {
when 1 -> { throw "oh no!"; }
when 2 -> console.log("oh no!")
}
As an expression, no blocks would be permitted on the right side of ->, e.g., the following would be a syntax error:
// SyntaxError
let a = case (x) {
when 1 -> { throw "oh no!"; }
when 2 -> console.log("oh no!")
}
Whereas it would not be a syntax error if all of them are expressions:
// Runs just fine
let a = case (x) {
when 1 -> (() => { throw "oh no!"; })()
when 2 -> console.log("oh no!")
}
Relatedly, return can only be used within a block, not loose within ->:
case (x) {
when 1 -> return x // SyntaxError
when 2 -> { return x; } // This line is good
}
when 1 -> do { } and go from there.->. Presumably, as part of the end game, we'd also relax things to allow you to use blocks in case expressions, with do expression semantics.How does this impact using break or continue in when blocks? (which i believe wa the motivation to change them to statements)
@ljharb In the above proposal, blocks in case statements are only supported in statements, not in expressions. Therefore, the problem does not arise.
I'm really happy to see this. I worry about adding more statements to an already statement heavy language.
Just a thought: if you made the values only non-sequence expressions, you could gain everything lost by just leveraging do expressions to let it all fall out naturally. This would also make it very easy to just rip out the statement form without sacrificing functionality - you could always use it as an expression statement like people already do quite frequently for some expressions (like function calls). I've also seen a few cases of people using logical expressions as statements in the wild in open-source code, usually of the form of f() && g() && h(); or f() || g() || h();.
For what it's worth, a bare parenthesized do expression as a statement (i.e. (do { ... });) is exactly equivalent to a block statement, and could even be desugared as such.
@isiahmeadows Agreed that this should combine well with do expression, but it also doesn't depend on them.
@littledan I'm implying it could be simplified by depending on them for returning statements. Alternatively, you could make it like arrow functions and do either a block or an expression (and IIRC this variant has precedent in Rust). Something like this:
case (x) {
when 1 -> expr,
when 2 -> { /* block */ },
}
This could be useful in both statement contexts (obvious) and expression contexts like this:
let value = case (x) {
when {value} -> value,
when _ -> { throw new Error("fail") },
}
what if the value had to be an expression, and people could use do expressions to put statements in? it feels like we're missing a lot with the current iteration, especially to those of us coming from other languages with pattern matching.
const value = case(x) {
{ t } -> v,
[asdf] -> do {
console.log('stuff');
thing(5);
},
};
@devsnek Beat you to the punch. 😉
What's the status of this suggestion? It seems to be broadly supported, judging by the above, so it would be nice to update the spec text to match. :)
I think it'd be a pity for expressions in branches not to be supported in a pattern-matching proposal, since the motivation of pattern matching stems from functional principles and I suspect the expression form is the most important in that context (the Redux example in the README illustrates this well).
@FireyFly There is language precedent for making it statement-based: Swift. Their switch is traditional typed pattern matching, but you can still explicitly fall through to subsequent cases and it has the C-like syntax of case Foo: ... for its cases. (It does break by default.)
I've got an alternate idea: what if you separated the pattern matching statement from the pattern matching expression? Specifically:
case statements can have either expression or -> { block } bodies, but the expression bodies are not allowed to be objects. (These are unlikely to exist anyways except potentially in future do expressions.)case expressions can only have expression bodies.This would dodge the do expression dependency while still allowing both variants.
@isiahmeadows isn't that precisely the suggestion in the topmost post in this issue?
@FireyFly Partially.
This is fantastic and I'll be updating the spec to add the expression form! Thank you!
This is implemented in https://github.com/tc39/proposal-pattern-matching/pull/140
Oh! You're right. I thought I'd bundled them together. Turns out I Just Didâ„¢ the changes directly: https://github.com/tc39/proposal-pattern-matching/commit/9434e145919955c1c9567993bb64857c0be7a255
So I'm just gonna close this one. Cheers~
Most helpful comment
This is fantastic and I'll be updating the spec to add the expression form! Thank you!