Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reconsidering spelling of if and else #208

Closed
mpcsh opened this issue Jun 28, 2021 · 45 comments
Closed

Reconsidering spelling of if and else #208

mpcsh opened this issue Jun 28, 2021 · 45 comments

Comments

@mpcsh
Copy link
Member

mpcsh commented Jun 28, 2021

As we discovered in #206, statements like the following have the potential to cause confusion:

match (res) {
  when ({ status: 200 }) { ok(); }
  when ({ status }) if (400 <= status && status < 500) { err(); }
  else { noResponse(); }
}

Specifically, the concern is that folks learning how to use pattern matching will misconstrue the if on the second clause as being attached to the else clause, which would change the mental model of execution here.

Do we have a way to investigate how prevalent this confusion would be? Perhaps using the cognitive dimensions of notation framework that Yulia and Felienne presented last year?

@ljharb
Copy link
Member

ljharb commented Jun 28, 2021

i think this example isn’t confusing due to the indentation, but it obv could be written in a confusing way:

match (res) {
  when ({ status: 200 }) { ok(); }
  when ({ status })
  if (400 <= status && status < 500) { err(); }
  else { noResponse(); }
}

(and of course, else if would be a syntax error, but that would be quickly discovered if attempted)

@theScottyJam
Copy link
Contributor

Along a similar veign:

match (data) {
  if (1) 'one'
  if (2) 'two'
  else 'other'
}

The second "if" is really behaving as an "else if".

I don't find these issues particularly confusing, and I feel others would quickly get used to them if they do initially find them confusing, but I also see value in exploring other keywords if people want to suggest alternative ideas.

@ljharb
Copy link
Member

ljharb commented Jun 28, 2021

@theScottyJam true but i don't think we should be concerned whatsoever with someone who's not using any matching, since you'd be able to do this without this proposal (your example would require curly braces on the RHS as well):

do {
  if (data === 1) { 'one' }
  if (data === 2) { 'two' }
  else { 'other' }
}

@max-hk
Copy link

max-hk commented Jun 28, 2021

How about otherwise?

@fedeci
Copy link

fedeci commented Jun 28, 2021

@MaxLOh isn't it a bit verbose?

@theScottyJam
Copy link
Contributor

theScottyJam commented Jun 28, 2021

@ljharb - That was a simplified example (and slightly incorrect example - I see I just put numeric constants in the if instead of comparing it with something). You can throw in a few when (...) ... at the start of that example to make it more complete, and make it so a simple do expression wouldn't replace it.

Either way, it's not a big concern of mine (nor is the original issue) - anyone who understands the rule that "only the first match expression that matches will execute" shouldn't get too tripped up by any of this.

your example would require curly braces on the RHS as well

Aren't you allowed to just put expressions on the RHS side without {}?

@ljharb
Copy link
Member

ljharb commented Jun 28, 2021

@theScottyJam no, not at the moment. see #181.

@mpcsh
Copy link
Member Author

mpcsh commented Jun 28, 2021

Reading all this back, I don't see a reasonable way to change the spelling of if. I think else is still the best option for its use; otherwise is alright but a bit verbose. Perhaps it's worth revisiting @tabatkins's proposal of _ in this new light?

EDIT: Speaking of, I was never clear on whether _ would simply replace else, or if it would be used like when _.

@theScottyJam
Copy link
Contributor

I guess there's one way to have an alternative spelling to "if" - in #182 if we go with the idea of taking out the when keyword, then we could change the word if to when, as when would become a free word to use.

For example:

// do this
match (x) {
  ({ a: 1 }) { 2 }
  ({ a: 2, b }) when (!isNaN(b)) { 3 }
  otherwise { 4 }
}

// instead of this
match (x) {
  when ({ a: 1 }) { 2 }
  when ({ a: 2, b }) if (!isNaN(b)) { 3 }
  else { 4 }
}

Just pointing this out, I would rather not do this, but it is an option.

@noppa
Copy link

noppa commented Jun 29, 2021

There's bunch of discussion about replacing else with something (I for one don't find otherwise too verbose), but how about replacing if with something else to avoid misunderstandings about where else attaches.
and, for example,

 match (x) {
  when ({ a: 1 }) { 2 }
  when ({ a: 2, b }) and (!isNaN(b)) { 3 }
  else { 4 }
}

@ljharb
Copy link
Member

ljharb commented Jun 29, 2021

and is likely to be used for chaining matchers (#179).

@waldemarhorwat
Copy link

and has the connotation of being commutative, aside from the order of evaluation. It would be really weird for the subexpressions x and y in xandy to use entirely different syntaxes.

@gibson042
Copy link
Contributor

gibson042 commented Aug 2, 2021

I'm also a bit uncomfortable with the possibility of … if ( expression ) { statements } else { statements } appearing in a context where the "else" is not coupled to the "if", even where the preceding context makes parsing unambiguous (for machines). And there are at least five general alternative approaches, each of which has an unfortunate amount of bikeshedding potential that is likely to dominate discussion:

Remove else

match (x) {
  when ({ a: 1 }) { 2 }
  when ({ a: 2, b }) if (!isNaN(b)) { 3 }
  // match everything using `if`
  if (true) { 4 }
  // match everything using `when` with an ignored binding
  when (_) { 4 }
}

Rename else

match (x) {
  when ({ a: 1 }) { 2 }
  when ({ a: 2, b }) if (!isNaN(b)) { 3 }
//↓───────↓
  otherwise { 4 }
}

(or rather than otherwise: any, fallback, catchall, etc.)

Rename if

match (x) {
  when ({ a: 1 }) { 2 }
//                   ↓──↓
  when ({ a: 2, b }) test (!isNaN(b)) { 3 }
  else { 4 }
}

(or rather than test: condition, having, where, guard, filter, etc.)

Rename and repurpose when

match (x) {
//↓───↓
  given ({ a: 1 }) { 2 }
//↓───↓               ↓──↓
  given ({ a: 2, b }) when (!isNaN(b)) { 3 }
  else { 4 }
}

(or rather than given: shape, destructure, with, etc.)

Rename and repurpose match and when

//───↓
select (x) {
//↓───↓
  match ({ a: 1 }) { 2 }
//↓───↓               ↓──↓
  match ({ a: 2, b }) when (!isNaN(b)) { 3 }
  else { 4 }
}

(or rather than select: map, test, switch.patterns, do.select, etc.)

@treybrisbane
Copy link

I agree that it's a bit weird... But it's really hard to know if it'll actually be a problem in practice... 🤔

In case an extra data point is useful, I personally have a slight preference for the when (_) { ... } approach. I don't really see the need to use a separate keyword for the catch-remaining case in match (in contrast with switch, where default is necessary due to case requiring an operand specifically with which to do an equality check).
But again, that preference is only slight. Ultimately I wouldn't care either way.

@ljharb
Copy link
Member

ljharb commented Aug 5, 2021

I feel pretty strongly that match is the only viable option for the primary keyword.

We could spell "when" differently, or "else", but there's a lot of aesthetic value from both of those having 4 letters imo :-) perhaps a 4-letter alternative for if?

@gibson042
Copy link
Contributor

gibson042 commented Aug 5, 2021

We could spell "when" differently, or "else", but there's a lot of aesthetic value from both of those having 4 letters imo :-) perhaps a 4-letter alternative for if?

Rename if

I feel pretty strongly that match is the only viable option for the primary keyword.

Can you provide detail about why you feel that way? If it's something like "consistency with other languages", then I don't understand your objection to renaming when to case for alignment with the others mentioned in the README that identify clauses by keyword (Python and Scala).

P.S. I also noticed that none of the other languages have a special catchall syntax, even the normally else-happy Python, which IMO provides a lot of support for removing else here.

@ljharb
Copy link
Member

ljharb commented Aug 5, 2021

The feature is called "pattern matching". match is what makes the most sense to me.

I think forcing the common case where a catch-all will be needed into using when (unused) is a huge wart that we should avoid introducing, even if no other languages have avoided that wart.

@mpcsh
Copy link
Member Author

mpcsh commented Aug 5, 2021

Also, to address case - it's a high priority of this proposal that there be zero overlap with switch.

@mpcsh
Copy link
Member Author

mpcsh commented Dec 6, 2021

At this juncture, the champions group is split between else and default. One of the primary design goals of this proposal has been to have zero overlap with switch, but there's significant support for borrowing from switch in this specific case.

@ljharb
Copy link
Member

ljharb commented Dec 6, 2021

In particular, while I remain unconvinced, a compelling argument against else is that there's a very strong mental coupling between if and else to JS developers.

Note that the syntactic overlap with switch worsens significantly iff we use default, have a RHS separator, and that separator is :.

@hez2010
Copy link

hez2010 commented Dec 8, 2021

How about:

match (res) {
  { status: 200 } => { ok(); },
  { status } when (400 <= status && status < 500) => { err(); },
  _ => { noResponse(); }
}

Or

res match {
  { status: 200 } => { ok(); },
  { status } when (400 <= status && status < 500) => { err(); },
  _ => { noResponse(); }
}

?

Also, the later one can be examined as an expression instead of a statement so it can be used in assignment:

const result = res match {
  { status: 200 } => 'ok',
  { status } when (400 <= status && status < 500) => 'err',
  _ => 'other'
}

@ljharb
Copy link
Member

ljharb commented Dec 8, 2021

@hez2010 as has been stated many times, _ is an identifier so it's not available; separately, => implies it has arrow function semantics, which it very much doesn't (there's no stack frame added, for example).

@mpcsh
Copy link
Member Author

mpcsh commented Dec 11, 2021

I've now been fully convinced that we need to change else. I was running through the recent proposal updates with a good friend of mine, who is a senior eng with lots of JS experience, and when we got to this issue we realized that she had indeed been connecting if & else. That convinced me that the association between the keywords is too strong for us to use them.

I'm on board with Yulia's idea of using default, and that this would actually be one case where we should borrow from switch. Though I feel more strongly about not using else than I do about using default in particular.

@bathos
Copy link

bathos commented Dec 13, 2021

This could be any word without ambiguity, right? If so, I’d suggest none.

none avoids switch vocabulary, but the main reason for suggesting it is that it doesn’t abruptly discard the declarative mental model that other language and syntax has been encouraging. It still describes a match condition — “none of the above.” There’s no need to jump to an ultra-imperative “instruction” word like else.

default is still better than else though. It really threw me off that “when...else” is gibberish. It’s not just regular gibberish — it’s specifically the sort that your brain backtracks on in order to “find the mistake”. no matter how many times I saw it, there was a little mental short circuit / hiccup / segfault.

@Jack-Works
Copy link
Member

none is interesting.

@ljharb
Copy link
Member

ljharb commented Dec 13, 2021

I don’t think “none” fits well; it doesn’t read like a sentence, and it implies nothing will match, but that very clause does

@theScottyJam
Copy link
Contributor

and it implies nothing will match

I think we might have swapped the word around wrong. Perhaps "anything" would be a more appropriate keyword if we want to go this more declarative route, implying that the "anything" matcher will match against whatever comes its way. Now that you point it out, I agree that "none" should technically be seen as "nothing will match this", in the pattern-matching mental model.

match x {
  when ({ y: 2 }) ...
  anything ...
}

@ljharb
Copy link
Member

ljharb commented Dec 13, 2021

anything… else?

@mAAdhaTTah
Copy link

Not 100% sure this works from a parsing perspective but what about using ... like "this matches against the rest of the potential patterns":

match (res) {
  when ({ status: 200 }) { ok(); }
  when ({ status }) if (400 <= status && status < 500) { err(); }
  ... { noResponse(); }
}

This might read better with the arrow separator:

match (res) {
  when ({ status: 200 }) -> { ok(); }
  when ({ status }) if (400 <= status && status < 500) -> { err(); }
  ... -> { noResponse(); }
}

@mpcsh
Copy link
Member Author

mpcsh commented Dec 13, 2021

I actually kinda like none!

Not a big fan of ... - I find it a bit hard to read and I'm not a fan of the inconsistency of having when be a keyword and ... be a symbol

@bathos
Copy link

bathos commented Dec 13, 2021

I don’t think “none” fits well; it doesn’t read like a sentence, and it implies nothing will match, but that very clause does

True, I can see how it's unclear now. The idea was to communicate "other" (descriptive of the match) instead of "otherwise" (descriptive of what to do? unsure of the right term). There are likely better ways to do that.

@ljharb
Copy link
Member

ljharb commented Dec 13, 2021

when else { … }

@bathos
Copy link

bathos commented Dec 13, 2021

if that is the only better way, I think we must not speak the same English. that reads like a hoofed turtle to me: it's confusing, it doesn't register that the two parts are connected.

@ljharb
Copy link
Member

ljharb commented Dec 13, 2021

lol true enough. it's a bikeshedding thread, ideas don't have to be good.

@bathos
Copy link

bathos commented Dec 13, 2021

Oh, I misread that as reiterating a preference for "else", not a new suggestion for "when else". I suppose the description I gave for how I perceive that still works, but I would have tempered its ... colorfulness if I'd realized it was a new suggestion.

@ljharb
Copy link
Member

ljharb commented Dec 13, 2021

elsewhen :-p

@bathos
Copy link

bathos commented Dec 13, 2021

elsewhen is a real word — as is the slightly more common (historically) “anywhen.” Both are probably too obscure/archaic, though it’s interesting that both have increased in usage during the last forty years.

forgive me for this but I couldn't help but imagine what the whole thing might look like with the olde h/th/wh words
hence (hitherable) {
  whence ({ status: 200 }) thither ok();
  whence ({ status }) where 400 <= status && status < 500 thither err();
  anywhence noResponse();
}

@haltcase
Copy link

haltcase commented Dec 14, 2021

@bathos might as well throw lest in the ring then!

match (res) {
  when ({ status: 200 }) ok();
  when ({ status }) if (400 <= status && status < 500) err();
  lest noResponse();
}

@mAAdhaTTah
Copy link

Was reading #259 & rereading this thread and wanted to add some additional suggestions that occurred to me, in case the champions group revisits this decision:

when(...): expr; // modification of a suggestion I made above
when: expr; // new suggestion, no matcher or parens at all

@tabatkins
Copy link
Collaborator

Hm, don't recall if we ever considered that. Doesn't look bad to me, personally.

@theScottyJam
Copy link
Contributor

theScottyJam commented Jun 17, 2022

when(...): expr isn't my favorite. I feel like ... should be reserved for spread-related concepts. Even this proposal is adding a bare ... syntax that can be used within array patterns.
when: expr could work, though it reads a bit funny. For example, read when: x > 2 out loud - it doesn't really do what it sounds like it should do.

Perhaps another option would be to provide a new global matcher. e.g. something like this:

when (Any): expr
// or
when (MatchAll): exp

Where Any or MatchAll are objects on globalthis that implement the matcher protocol.

Edit

Forgot that those custom matchers would need to be "interpolated" like this

when (${Any}): expr
// or
when (${MatchAll}): exp

...which is much less desireable

@ljharb
Copy link
Member

ljharb commented Jun 17, 2022

I definitely don't like the same token being used for "to match something" as "the default matcher".

@Jack-Works
Copy link
Member

What about finally?

match (res) {
  when ({ status: 200 }): ok()
  when ({ status }) if (400 <= status && status < 500): err()
  finally: noResponse()
}

@ljharb
Copy link
Member

ljharb commented Jun 17, 2022

That implies it always runs after the matched clause, just like in try/catch.

@tabatkins
Copy link
Collaborator

Oh, I misread the earlier comment - I thought the when(...) line was just to illustrate a preceding matcher. I also don't like when(...) as a nil matcher.

The one I didn't think looked bad was just the plain when keyword by itself: when: defaultValue;.

(I'm still weakly supportive of keeping default as it is, I'm just open to exploring other things since multiple people have reservations.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests