Proposal-pattern-matching: Idea to reuse switch-case syntax

Created on 9 Dec 2018  路  21Comments  路  Source: tc39/proposal-pattern-matching

Hi!

There is already a suitable syntax in JS for pattern matching - switch-case. We need to add just some features to it for create a live pattern matching example.

  • Make switch return the value from case branches.
  • Allow to use in case conditions non-primitive expressions.

Example (old syntax):

const getLength = vector => {
  case (vector) {
    when { x, y, z } ->
      return Math.sqrt(x ** 2 + y ** 2 + z ** 2)
    when { x, y } ->
      return Math.sqrt(x ** 2 + y ** 2)
    when [...etc] ->
      return vector.length
  }
}

Proposed syntax in the issue:

const getLength = vector =>
  switch(vector) {
    case { x, y, z }:
      Math.sqrt(x ** 2 + y ** 2 + z ** 2)
    case { x, y }:
      Math.sqrt(x ** 2 + y ** 2)
    case [...etc]:
      vector.length
  }

Most helpful comment

See my comment here as well.

switch/case is unintuitive, and causes many bugs, and it needs to die in a fire and leave behind no remnants. If there鈥檚 any similarity to switch/case, pattern matching will be harder to teach, and it鈥檒l be harder to divide cleanly between old and new coding patterns.

All 21 comments

See my comment here as well.

switch/case is unintuitive, and causes many bugs, and it needs to die in a fire and leave behind no remnants. If there鈥檚 any similarity to switch/case, pattern matching will be harder to teach, and it鈥檒l be harder to divide cleanly between old and new coding patterns.

See my comment here as well.

switch/case is unintuitive, and causes many bugs, and it needs to die in a fire and leave behind no remnants. If there鈥檚 any similarity to switch/case, pattern matching will be harder to teach, and it鈥檒l be harder to divide cleanly between old and new coding patterns.

Why don't you like switch construction?

Mainly because it鈥檚 been the cause of many production bugs in codebases ive worked with, largely due to fallthrough and people thinking that case statements implicitly have a scope.

Would there be support for an explicit fallthrough? For example:

const res = await fetch(jsonService)
case (res) {
  when {status: 200, headers: {'Content-Length': s}} -> {
    console.log(`size is ${s}`)
  }
  when {status: 403} -> continue
  when {status: 404} -> {
    console.log('Some error...')
  }
}

I used the continue keyword here just as an example, I don't know what keyword would be the best option.

Mainly because it鈥檚 been the cause of many production bugs in codebases ive worked with, largely due to fallthrough and people thinking that case statements implicitly have a scope.

People just should read the documentation more accurately I suppose..

I'm with you there :-) all of our lives would be much easier if we all universally read documentation and followed best practices. Human nature being what it is, though, it's important to try to design language features (and library APIs) that can be easily used properly, but are hard to misuse.

I'm with you there :-) all of our lives would be much easier if we all universally read documentation

@ljharb

I asked because at first it had some fallthrough semantics and it's hard to know what is the current consensus of it as sometimes the docs might not have been updated. If I take that the current documentation says:

It is the opinion of the authors that fallthrough, though often mentioned, is neither essential, nor as straightforward or as convenient as it might sound.

Then ~that's a lie~ I disagree, the example I gave is pretty straightforward even for beginners, limiting the language won't prevent bad programmers from doing bad code, you solve that with code reviews and testing.

There are situations that a fallthrough will make the code way more readable than a or/and clause.

Mainly because it鈥檚 been the cause of many production bugs in codebases ive worked with

Catching a bug in production because of a switch statement is a clear indication of lack of testing, I personally have done that, but I don't blame the language, only myself.

@borela "that's a lie" is unnecessarily hostile; perhaps you meant "i disagree with that"?

"straightforward even for beginners" is a much harder thing to prove than "confusing for anyone anywhere", and yes, limiting the language definitely restricts the possibilities for bad code (although, obviously, never removes the possibility).

In those situations (where you allege that fallthrough makes the code more readable), do you mean, assigning the same logic to multiple patterns? This proposal offers that without fallthrough - would that address your concerns?

@ljharb

"that's a lie" is unnecessarily hostile; perhaps you meant "i disagree with that"?

Yes, english is not my mothertongue, sorry if that sounded aggressive, I fixed it.

"confusing for anyone anywhere", and yes, limiting the language definitely restricts the possibilities for bad code

I had more troubles with if statements than the switch, and the root cause was me forgetting to test an branch.

assigning the same logic to multiple patterns? This proposal offers that without fallthroug

You mean this?

`` case (res) { when {status: 200, headers: {'Content-Length': s}} -> { console.log(size is ${s}`)
}
// Imagine I need to match against a list of specific status codes and it is not possible to specify with a
// range.
when {status} if (y === 403 || y === 405) -> {
console.log('Some error...')
}
}

Yep! You could also use a set of codes (to get the same === comparison switch has), or an array with .some, or a custom predicate function, etc. Does that address your use cases for fallthrough? If not, could you provide some code illustrating them? I鈥檇 be very interested to learn.

It does somewhat, in some instances I like for the list of values to be explicit specially when dealing in accounting systems where there are some exceptions on how it should generate invoices based on company's classification and products.

I get your point, I'll be able to do that with predicates.

I am actually curious what syntax C# 8.0 will choose for pattern matching: https://neelbhatt.com/2018/05/19/c-8-0-expected-features-part-iii-switch-statments/ It seems likely, the final syntax will look something like this:

int getCount(IEnumerable<int> items)
{
    return items switch {
        case [] => 0,
        case [x, ...y] => 1 + getCount(y)
    };
}

While an example based on C# may seem irrelevant to this discussion, it is important to recognize this may be one of the first C-based languages to adopt match clauses into the language. We should try to gain whatever insight we can.

The true advantage to this syntax is it reuses existing keywords. It doesn't fall pray to issues related to fall-through cases, since each case should be an expression. In other words, the break keyword would most likely be illegal and unnecessary.

This syntax using case would also be useful if JavaScript ever decided to support "partial functions" that are only valid for certain inputs, as are popular in Haskell and Scala. In which case, the new proposed when keyword couldn't exist purely within a case expression. It'd be good to avoid the use of a contextual keyword, if possible.

I was thinking about this recently, and in the spirit of keeping switch syntax:

switch (value) break {
  case "one":
    console.log("one")
  case "two":
    console.log("two")
  case "three":
    console.log("three")
}

The break between ) and { would signal the break by default. Yes, this looks odd. Let it sit with you for a bit. Then think about how many times you've missed a break resulting in a bug.

The only other addition to this is multiple values on case, such as case 1, 2, 3:. This would also work on a normal switches as an alternative to case stacking.

What I like about the break signal is it is easy to spot, much easier than that one case with a missing break.

This would also work on a normal switches as an alternative to case stacking.

If you mean that the "multiple values on case"-syntax would be added to existing switch statements, I'm pretty sure that won't be possible.

switch (3) {
  case 1, 2, 3: console.log('wat');
}

is already in the language and equivalent (ignoring possible side-effects in case clause) to

switch (3) {
  case 3: console.log('wat');
}

I get that the comma operator is technically valid inside a case. Some questions about this:

1) Are there any legitimate uses cases for using the comma operator in this context?
2) How many instances in the wild are there of this?

I would think this is not something you'd find in the wild. The behavior from the example is surprising, especially considering other languages allow the use of a comma for multi-value cases. Given that, I think people would avoid the syntax all together to avoid confusion, even if they had a legitimate case for it.

Also, you can use a comma in variable declarations, and this is treated differently. So I would think that treating a comma differently in a case really isn't that unreasonable. I think it is an important first step.

The only concern I have is running this in an older interpreter. Since it is technically valid syntax, it could lead to bugs. So the concern is not that there is uses of this in the wild today, but it could produce unexpected behavior with new code in older engines. I would like to think that with linters and transpilers this could be greatly minimized, but a concern none the less.

the risk is too high; the many use cases of pattern matching aren鈥檛 met by it; and some of us (like me) won鈥檛 accept anything that doesn鈥檛 utterly divorce itself from switch case syntax.

Honestly the biggest argument against this looks really subjective, without any data or measurement to back it up.

Yes the switch syntax the first times you encounter can be misleading, but if you didn't figure out how to use it by now given that it is present with the same shortcomings in so many different languages then I would argue that more than an API issue is a programmer issue.

The objective data is that more languages outside js implemented/are implementing pattern matching with the switch syntax, and if we talk about API simplicity I would argue that one of the biggest factor is familiarity with what you already know compared to learn new ones like the proposed case/when.

@MastroLindus in the fullness of time, i believe there are more developers for whom JS will be their first and only language than JS devs who are coming to it from other languages. What other languages do is an important consideration, but JS needs to do what鈥檚 right for JS.

@ljharb agreed that JS needs to do what's right for JS. I am simply arguing that not choosing a common syntax from other languages in favor of a js-specific one based mostly on personal taste and subjective views might not be the best idea, and honestly I would say it's a bit arrogant.

I am not saying that the proposed API is bad, I personally don't like it but again, it's also my subjective and personal view.

What I would like to see is a bit more objective/data-driven approach, something like some numbers/data about the negative impact of switch syntaxes in existing codebases, some numbers of how many js developers don't know it/have troubles with it, or at least a simple, democratic preference poll.

I am happy to accept any proposed API, that I like or not like, as long as I see that the clear majority of people prefers it, or that there are good, objective arguments in favor of it, but in this case I didn't see neither

Was this page helpful?
0 / 5 - 0 ratings

Related issues

samuelgruetter picture samuelgruetter  路  3Comments

tabatkins picture tabatkins  路  6Comments

Haroenv picture Haroenv  路  6Comments

caub picture caub  路  8Comments

skfreddo picture skfreddo  路  6Comments