Proposal-pattern-matching: replace else with default.

Created on 27 Jun 2021  Â·  19Comments  Â·  Source: tc39/proposal-pattern-matching

As said here, it can be confusing if "else" block is continue of "if" block or of "when" block, so instead, I think, it would be great to replace it with default (as in switch) so it's separated, and not confusing.

design idea

Most helpful comment

Regarding your comment to Jordan: while that is a correct analysis of if-statement semantics, it does not apply to this proposal, as we've already discussed. I understand that there is syntactic confusion due to reusing that keyword, which is a reasonable point of discussion. I will raise a separate issue specifically for that matter.

All 19 comments

The word “default” is associated with switch, and thus the overlap wouldn’t be acceptable. There must be no overlap with switch.

Understand, still I think, there should not be "else", but something else. for example maybe other {}?

else follows whatever came before it; I’m not sure i see the confusion angle.

I can see a bit of the reason why "else" can potentially be confusing, but I'll share some reasons why I think it's not actually a big deal. I'll use the following example:

match (x) {
  when (0) 'zero'
  if (x > 0) 'positive'
  else 'negative'
}
  • As long as you remember that the first branch arm that matches gets executed, and nothing else, then either interpretation of "else" will lead you to a correct understanding of the above example: Does the else apply to the whole match? Or only to the previous if? Either way, we'll only reach the "else" clause when x is negative. If you're working under the false assumption that all branch arms that matches will execute, then you'll also incorrectly interpret a match expression that simply has two ifs, thinking that both arms can execute (I don't think the way we're using an "else" here is any worse than the fact that sometimes an "if" in the match expression is really more like an "else if").
  • The way we're using "else" here does not go against our natural language usage of words such as "else" or other synonyms like "other" or "otherwise". e.g. in the following sentence "when _ then _, and if _ then _, else _", depending on context, it's ok for that "else" to apply to the whole thing.

I suppose when it goes if when else, there’s a question - but else doesn’t go after if, it goes after if or else if. In other words, else already means “if nothing else has been selected, use this one” - which is what it means here.

image

as in this image,... else can mean two things:

when res contain status, we execute if-else, if res doesn't contain status, we do nothing.
when res contain status, we execute if, if res doesn't contain status, we execute else.

is else continue of if in when ({status}) or is is another "branch" of match?
That is really two different thing that can be confused.

I think, we should let else keyword to be used for if-else, and for when-(else) we should use something else.

Ah ... I see now, you're talking about parsing ambiguity. So, to restate your question, in the following example:

match (res) {
  when ({ status }) if (status >= 400) {
    throw new RequestError(res)
  }
  else {
    throw new RequestError('...')
  }
}

there's two ways that the parser could interpret it.

one: when res contain status, we execute if-else, if res doesn't contain status, we do nothing.

match (res) {
  when ({ status }) {
    if (status >= 400) {
      throw new RequestError(res)
    } else {
      throw new RequestError('...')
    }
  }
}

two: when res contain status, we execute if, if res doesn't contain status, we execute else. (Maybe I'm mis-understanding what you meant by this, in which case, you can correct me, but here's what I think you're thinking when you said that)

match (res) {
  when ({ status }) {
    if (status >= 400) {
      throw new RequestError(res)
    }
  }
  else {
    throw new RequestError('...')
  }
}

And you're wanting to use a default keyword to ensure the parser always correctly picks the correct version, and we remove this ambiguity.

Actually, neither of those two options are the correct interpretation for what that code sample does. The truth is, unlike if, while, for, etc, the only thing that can come at the end of a match branch is either an expression, or curly brackets that contain a do block. Without curly brackets, you're not allowed to put things like an if in that location. In the code example, the if isn't what gets executed if the match succeeds, instead, it's literally an optional part of the match syntax that allows you to add an arbitrary condition to the match, which, if the condition fails, we'll keep looking for another branch arm to execute. It's the difference between having the if in the body of the code that executes, and having the if as part of the match condition.

To illustrate, in the following example, result will be equal to 'second'. If the if was part of the match body, then the first when would match but the if would fail, so neither first nor second would be given back.

const result = match ({ x: 10 }) {
  when ({ x }) if (x < 5) 'first'
  when ({ x }) 'second'
}

Does that make sense? Am I understanding your concerns correctly?

@Mlocik97 an “else” covers anything not matched before it, whether that’s a “when” or an “if”. The “if” in that image is attached to the preceding “when”, and not to the following “else”. Each match clause is distinct, and only has a single RHS (the curly-braced section).

@theScottyJam yes, you understand it correctly. And sorry for confusion, I'm not really good with English. About that "if" if I understand it correctly, would it be better if when would be function, where we will return something in body, and body would have ability to contain if-else, switch, or even other stuff?

const result = match ({ x: 10 }) {
  when ({ x }) => { if ( x < 5) return 'first' }
  when ({ x }) => 'second'
}

that can be simplified to (but is not same):

const result = match ({ x: 10 }) {
  when ({ x }) => x < 5 ? 'first' : 'second'
}

or simplified like this, and is same:

const result = match ({ x: 10 }) {
  when ({ x }) => { if (x < 5) return 'first' }
  return 'second' // like else or default
}

or something similiar?

it would also allow more complex logic inside when blocks.

To expand a bit on Jordan's response, "if-else" and "when-else" are not distinct concepts within this proposal. So specifically:

when res contain status, we execute if-else, if res doesn't contain status, we do nothing.

Is not a valid execution under our current semantics; your other interpretation is correct. The else is not tied in any way to the if inside the previous clause.

There's no parsing ambiguity here because else can only appear as the last clause in a match statement, and it doesn't matter what precedes the else. It's worth noting that this has precedent from other languages with pattern matching constructs.

Is not a valid execution under our current semantics; your other interpretation is correct. The else is not tied in any way to the if inside the previous clause.

yes, but even if I would know this, when reading code where I see if() {} else {}, I would always think, else is connected to if.
Because to now, it always was used like that.

I'm potentially open to changing the spelling of some keywords to avoid this confusion, because we should not imply the relationship that you're describing.

To address the comment about functions: I don't see how making when clauses into functions allows for _more_ functionality than the current semantics. In fact, it would disallow:

  • Pure if clauses (i.e. an if without a when, which is currently allowed)
  • return / break / continue from an outer context

It would also incur the performance overhead of a function for each clause, which is not negligible when compared to do expressions.

@ljharb yes, but it doesn't matter if it's if or else if,... it's still part of typical if() {} else if() {} ... else if() {} else {} chaining. Btw. for me else if () {} is same as else { if () {} } in order of execution, yes, it has different scope so it's different in that, and also in few other things, but in some way, we can take if and else if as the same thing...

@mpcsh about if, that's not problem... about return/break/continue you are right, hmm, then the only thing I have on mind is create new keyword.

Currently, the following is allowed:

match (num) {
  when (0) { foo(); }
  if (1 <= num && num < 10) { bar(); }
  when (10) { baz(); }
  else { quux(); }
}

I think this is important functionality, so I would say it _is_ a problem to disallow it.

it would not disallow it... in my example, it would be as

match (num) {
  when (0) => foo()
  if (1 <= num && num < 10) bar()
  when (10) => baz()
  return quux()
}

or something like this. But it was just my "idea", don't take it like it's need to be like that, just some inspiration you can take.

Regarding your comment to Jordan: while that is a correct analysis of if-statement semantics, it does not apply to this proposal, as we've already discussed. I understand that there is syntactic confusion due to reusing that keyword, which is a reasonable point of discussion. I will raise a separate issue specifically for that matter.

From an implementation perspective, having some clauses be functions and others be statements is intractable. We considered such an idea early on but quickly had to move away from it.

ok, thanks...

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Haroenv picture Haroenv  Â·  6Comments

arcanis picture arcanis  Â·  6Comments

mheiber picture mheiber  Â·  5Comments

robertkowalski picture robertkowalski  Â·  4Comments

tabatkins picture tabatkins  Â·  6Comments