const res = await fetch(jsonService)
case (res) {
| {status: 200, headers: {'Content-Length': s}} ->
console.log(`size is ${s}`),
| {status: 404} ->
console.log('JSON not found'),
| {status} if (status >= 400) -> {
throw new RequestError(res)
},
}
```js
| {loading} ->
| {error} ->
| {data} ->
}
}
This seems fewer characters to type and avoids adding new keywords to the language
This also works naturally with `or` pattern
```js
const f = x => case (x) {
| 0
| 1
| 2 -> "low precedence"
| 3
| 4 -> "high precedence"
}
f(0)
I have seen https://github.com/tc39/proposal-pattern-matching/pull/140, and it doesn't make sense to me, because match clause isn't really destructuring, so prefixing it with let, var, const would only confuse (also if this was made to avoid adding keywords?)
I still object to using case; i insist on adding a new keyword, since it鈥檚 a new concept.
Using a pipe for the conditions might work, but it seems pretty opaque - code is read much more often than it鈥檚 written, so I鈥檇 prefer to see us optimize for reading and understanding, not for writing.
I'd rather type when or case instead of |, typing a short word keeps me more in the flow than using characters which need modifier keys (on my layout at least..). It also reads much nicer with a keyword and needs less explanations.
I don't see how using a pipe is opaque, it's pretty expressive and natural, this is JavaScript, not Basic. Take spread operator as an example, it'll become second nature in a couple of days of using it.
The pipe is already used for logical or, binary or, maybe the pipeline operator, I personally feel there's enough pipes already.
@topaxi Yeah, but I've seen plans for || or | anyway. So why not embrace it fully
@topaxi pipeline operator uses |>
| is still the bitwise OR operator, and is visually similar to || and |>. It also doesn't convey what it does, to someone unfamiliar with reasonml or ocaml.
@ljharb fair point, but I think most of users know about TypeScript/Flow unions, where it is used as OR, bitwise OR operator also should be intuitive that it is match clause OR match clause OR match clause
I can understand if there's ambiguity in parsing |, but is | really unfamiliar? If pipeline lands would |> also be unfamiliar?
@goodmind on the contrary, i'm pretty certain most JS users have never even seen a typed language.
@ljharb what about bitwise OR? I mean it's still OR so should be familiar enough
most javascript-problems require rewriting your code/api so frequently that trying to lock things down with type annotation is generally anti-productive
the familiarity is that it coerces two operands to a 32 bit integer and produces a new one. How is that similar to your suggested usage?
@ljharb familiarity in name, OR, it's basically match that or that or that
People know semantics more than names.
@kaizhu256 this isn't about types
How do you plan to parse these?
The former is 2 cases written inline, the latter is a single case with a bitwise | operator and an object literal. The second is of course nonsensical, you'd never want to do a bitwise or with an object literal; but it is still syntactically correct and needs to be differentiated.
case (props) {
| {foo} -> foo | {bar} -> bar
}
case (props) {
| {foo} -> foo | {foo}
}
How would you differentiate a case | from a bitwise | inside an expression inside a case? Would you treat it like other statements that require you to either use a newline or ; to separate the two (and the first would not be valid unless it was | {foo} -> foo; | {bar} -> bar? Or would you force the parser to do a long lookahead to tell the difference? i.e. when it hits the | in both do a lookahead past the ambiguous {foobar} until it hits a non-ambiguous token like -> (case), if (case), } (exp), etc.
@dantman is parser recoverable? I think lookahead seems most plausible, but if it is too inefficient then newline is fine for me, so no inline cases
Symbol-heavy syntax makes things less accessible for new folks. For example, everyone gets what an if/else statement does, but ternary syntax can be really intimidating.
This feature's API is already somewhat complex. Power user ergonomics and parity with type notation + other languages aren't without merit -- I dig those points frealz -- but they are somewhat esoteric premises, and IMO it's not worth the cost to inclusiveness, especially since | has contextual nuances elsewhere in the language's API.
I strongly favor when in this case*, but could withhold protest if | were considered as a shorthand.
Thanks!
Edit: *Oh boy, no pun intended
I think @dantman nailed the key issue with using the pipe symbol for pattern matching. Parser efficiency and even just how the parser would differentiate the case pipe from bitwise pipe.
If a symbol was desired, it might be worth trying to use something more like the pipeline operator:
|>
Of course, if someone then tried to use a pipeline within a matching clause, you would have similar problems. So perhaps:
||>
Interestingly, this is ligatured with Fira Code (both are, actually):

| feels cleaner and easier to read for me than when. If we replace when by | and case by match it will be nearly ideal
what about |:?
const fn = (vector) ->
| ({ x, y, z }) => x + y + z;
| () => new Error('vector cannot be empty');
fn({ x: 2, y: 2, z: 2 }); // 6
fn(); // Error('vector cannot be empty')
Instead of defining a single const function, and replace switch with case there should be a way of defining multiple definitions of the function itself, so the different implementations would match on the patterns
Sorry, when I see
case (x)
when 1 -> ...
when 2 -> ...
Its like ifs but longer words, too much noise..
I would prefer something simpler, and easier to read and understand
fibo: 0 -> 0
fibo: 1 -> 1
fibo: x -> fibo(x - 1) + fibo(x -2)
fibo(10)
@webdeb this issue is about the pros and cons of using | as a token instead of a keyword such as when. It's probably best to keep the issue focused on this, rather than stray into unrelated topics. Additionally, at its core the proposal is about a pattern-matching construct--I think "syntactic sugar for defining functions", whilst somewhat related, is mostly orthogonal to that.
Is there any reason why a symbol or keyword is necessary at all? For example, Rust doesn't use anything and it's been fairly easy to read. As a reader, it's pretty obvious that the pattern is on the left of the -> and the expression on the right.
Rust uses , to terminate the statement, but it could be a newline/; here.
I think there really needs to be some kind of keyword or symbol in order for the parsing to be efficient. Parser efficiency has been a concern in other proposals as well, as js is interpreted/compiled on the fly, rather than ahead of time like Rust. This same parser efficiency concern is also part of the reason why we cannot necessarily just rely on local context to change the meaning of existing symbols, like |. Contextual meaning often requires more complex and slower parsing logic, so overloading existing operators may not be a viable solution.
Outside of how the syntax feels ergonomically, should there be some consideration over the paradigm that this syntax is aiming at? Examples of the when/case keywords are more like imperative style branching logic, which will be more accessible to developers less familiar with functional programming, but also feels less like pattern matching you find in some other functional languages that already have pattern matching as an important part of the paradigm, such as Haskell (although parser efficiency is less of a problem for Haskell).
TL;DR Omit when. Acknowledge _union types_.
Like @IceSentry , I ask why when is necessary; it can be omitted and still parse. Be terse.
The second issue, which was illustrated by OP but not stated by anyone is _fall-through_ or _multiple cases_ in the pattern.
Two separate issues:
Now for the semantics of (2):
:x: fall through
:heavy_check_mark: union type
The pattern defines a _type_ (with bindings), and a type defines a set of allowed structures. So, it is natural to regard multiple cases as a _union type_, which makes it obvious why | is used there. Again, the pattern defines a _type_ with _bindings_; the guard is an _expression_. The meaning of | in types vs expressions is distinct and clear -- see TypeScript. Then finally, the allowance of a preceding | in a union type definition is for aesthetics, as illustrated in OP.
So in the case when the union type has only a single item in the union, the preceding | is not a case-commencing token; it is a _union type_ syntax, which is aesthetically pleasing when aligned vertically.
Terseness is not a virtue; clarity and understandability is. Code is written far less often than it is read, and thus optimizing for writability is an antipattern.
Code is written far less often than it is read
That's why we prefer | over when. That's why fonts with ligatures exist. Symbols with special meaning may be harder to read when you see them for the first time in your life. But after some time it becomes easier to read symbols, than just words. Why do we use braces {} instead of begin/end blocks? why did we add arrow functions, instead of adding yet another keyword to use together with function? Using letters instead of symbols != having better readability
@ljharb +1 on that... Terseness for the sake of terseness is not a good idea.
But it's IMO very doubtful that bloating your code with dozens of useless whens will make it clearer and more understandable.
I too think that something shorter would be nicer to read.
That said, I'd like to see pattern matching in JS so much that I wouldnt mind any syntax 馃槉
To elaborate upon @PinkaminaDianePie's comment, I think there's a faulty implication in some comments here that verbosity inherently increases readability, which is absolutely not a universal truth.
How you define it on a case-by-case basis is relatively subjective, but I think that the readability for someone who's already aware of the syntax will be better with the terser syntax. The verbose syntax is optimising for readability specifically for the unfamiliar, which is in my view _questionable_ at best.
Most languages don't have a token in front of the cases at all (and sometimes | is used to join cases to a common body). Do we need to have any token here?
| indeed is most useful as a way to join multiple patterns, so using it for any pattern wouldn't really make sense.
Yes, there are use cases to match against an expression and also a pattern, which are two patterns that must be distinguished. There's also the scenario of a guard without a pattern, and also a default/else/fallback case - so there will have to be some kind of token.
How it's spelled isn't particularly important in stage 1 - that's a stage 2 concern.
From most languages I have seen, the pattern matching takes an optional guard. This allows for the bound variables to be used in the guard's expression, to further narrow the match beyond what is possible to express with type syntax alone.
For example:
case (foo) {
[a, b] if (a < b) -> [a, b],
}
@ljharb Aesthetics is certainly important, which is why I specifically mentioned it.
Right but there should also be support for an if without a pattern - ie, avoiding unconditional match clutter.
there should also be support for an if without a pattern
I was going to mention this, as well as the catch-all case having no pattern and no guard. Then I thought maybe it was intentional in other functional languages to have to write _ if (...) -> and _ -> in those cases.
The proposal has been updated in #174. It uses | as a pattern combinator, and when (<pattern>) to begin clauses (a bare guard, starting with if, is also permitted; as is else).
Please file a new issue if, taking into account the updated proposal, you have any concerns.
Most helpful comment
I still object to using case; i insist on adding a new keyword, since it鈥檚 a new concept.
Using a pipe for the conditions might work, but it seems pretty opaque - code is read much more often than it鈥檚 written, so I鈥檇 prefer to see us optimize for reading and understanding, not for writing.