Civet icon indicating copy to clipboard operation
Civet copied to clipboard

Is there any way to fall through in a switch?

Open gwhitney opened this issue 2 years ago • 4 comments
trafficstars

There are cases where falling through in switch statements keeps code DRY; for example, if there is some processing needed when a special condition is met and further processing when a more general condition is met. As a toy example,

description .= ''
switch n
   2
      description = 'prime'
      // FALLTHROUGH
   % 2 is 0
      description = 'even ' + description
   else
      description = 'odd'
description += ' number'

As far as I can see/understand there isn't anything I can currently write in place of // FALLTHROUGH to have the desired effect that when n is 2, the description will be even prime number. I guess it's not 100% clear what JavaScript this would compile to, maybe

let description = ''
if (n === 2) {
   description = 'prime'
   // FALLTHROUGH
   description = 'even ' + description}
else if (n % 2 === 0) {
   description = 'even ' + description}
else {
   description = 'odd'}
description += ' number'

where at least Civet is doing the code duplication for me so that I can specify the behavior in a DRY way. Is there any fallthrough mechanism that I missed, or possibly interest in adding one? I certainly agree that breaking should be the default and falling through would require some special notation if allowed at all.

gwhitney avatar Aug 30 '23 00:08 gwhitney

There's not currently any way to fallthrough using Civet's pattern matching. You can fall through using the JS style switch and case clauses but they won't be able to mix and match with pattern matching.

Having some kind of fallthrough keyword and duplicating the block in pattern matching seems like a reasonable feature to add eventually.

For these kinds of things I often use a pattern like this if I have many checks and am trying to go super DRY:

checks := [
  [(& is 2), "prime"]
  [(& % 2 is 0), "even"]
]

checks.reduce ([v, descriptions], [check, description]) ->
  [v, [...descriptions, ...(check(v) ? [description] : [])]]
, [n, []]
// then fill in else description if descriptions list is empty

... which has some tradeoffs of its own. But I think that the [(& % 2 is 0), "even"] predicate trick is pretty handy.

STRd6 avatar Aug 30 '23 03:08 STRd6

Inspired by continue label and #362 (which offers e.g. for as shorthand for the nearest for loop you're currently in), perhaps a reasonable notation for "fall through" would be continue switch, given that we want the opposite behavior of break? (I checked and switch is not a valid JS label, so this currently has no meaning.)

We could even do crazy things like continue switch in the middle of a case:

switch x
  when 0, 2, 4
    console.log 'even'
    continue switch if x is 0
  when 1
    console.log 'odd or zero'
---
switch (x) {
  case 0: case 2: case 4: {
    if (!(() => {
      console.log('even')
      if (x === 0) return true
    })) break
  }
  case 1: {
    console.log('odd or zero')
  }
}

I'm a little less clear on how to fall through with pattern matching, but we can presumably do it with additional conditionals in the if chain (and it will no longer be else if in all cases). Might be worth asking whether this kind of thing is worth doing, but I think it's doable. 🙂

edemaine avatar Nov 30 '23 21:11 edemaine

I really like the syntax continue switch to go on to the next test. Very readable/intuitive as to the intended semantics.

As for a use case, I usually use fallthrough when there are two similar cases, one of which just needs a little preprocessing to put it in the form of another. Do I have a specific example with pattern matching in mind right now? No, but I am pretty sure I wanted it at one point, which is why I filed this proposal. Next time I wish I had it, I will try to post as real an example as I can here.

As for how to implement continue switch with generic conditions, seems like the path of least resistance would be to count up the number of conditions, first test each of the conditions to find the first true one, giving you a "case number", and then dispatch on the case number to the desired code. That is, just reduce to ordinary switch on an integer:

switch x
   /pat1/
      behavior 1
   /pat2/
      behavior 2
      continue switch
   else defaultBehavior()

would become

let freshCaseVariable = 0
if (typeof x === 'string' && /pat1/.test(x)) freshCaseVariable = 1
else if (typeof x === 'string' && /pat2/.test(x)) freshCaseVariable = 2
else freshCaseVariable = 3 // since the else behavior is always chosen if none of the patterns match
// if there was no else in the switch then there just wouldn't be a final else in this if...else if...else if... block.
switch (freshCaseVariable) {
   case 1: 
      behavior(1)
      break
   case 2:
      behavior(2)
      // break suppressed because of continue switch
   case 3:
      defaultBehavior()
}

and of course you just generate the fancy code you showed above in the proper case number, in the situation that one of the civet cases has a "continue switch" in the middle of it. As far as I can see, there's barely any overhead in this object code over what you might write by hand.

I think continue switch would be a very nice addition to Civet!

gwhitney avatar Dec 01 '23 00:12 gwhitney

I just noticed this issue exists.

Swift has continue <label> and break <label> for nested loops, but for switch statements it literally just has a dedicated keyword fallthrough. fallthrough is a little more explicit than continue switch, but I suppose at the expense of an extra keyword.

The main thing I don't like about repurposing continue is the control-flow implications. fallthrough is only allowed as the last statement in a branch; allowing continue switch followed by other statements could be confusing to navigate.

I searched my entire codebase (several thousand lines, mostly C++) for places that I'm intentionally falling through a switch statement, and I'm only doing it twice. Both times the branch it's falling into is only a single statement (plus break), so make of that what you will.

bbrk24 avatar Dec 01 '23 02:12 bbrk24