An alternative to the switch statement

I am proposing an alternative to the switch statement: should/be/else

A switch statement can be very compact if each case fits on a single
line. It would, however, be nice if each case would have a code block
with curly braces. We can actually do it this way already:

switch someInt {
case 3, 5: do { ... }
case 7, 9: do {
    // here is
    // a multi-line
    // code block
} default: do { ... }
}

With a should/be/else it would look like this:

should someInt be 3, 5 { ... }
be 7, 9 {
    // here is
    // a multi-line
    // code block
} else { ... }

The else block can, of course, also be expanded into a
multi-line block. It could also be left out completely.

1 Like

switch already supports multi-line code blocks without curly braces. You don't need do to get that:

switch someInt {
case 3, 5: ...
case 7, 9:
  // here is
  // a multi-line
  // code block
default: ...
}
2 Likes

Yes, thank you, Karl, for the swift reply! I have used
the switch like that, but I should have pointed out
that I want something that is more like the usual
type of statements.

I see, so you actually want the curly braces, but the word do is a problem?

Other than the different names, would there be any other difference to switch?

That is correct, I want the curly braces.
The should/be/else is less busy. I also do
not like to be forced to use the default
together with a break, if I don't need it.

How's that different from the proposed else?

Alexander, In my very first message I wrote:

The else block can, of course, also be expanded into a
multi-line block. It could also be left out completely.

If I misunderstood your question, then please ask again.

I see, you want the ability to leave it out.

Hmmm then I think this leaves this proposed syntax in a strange middle valley. It's not as terse as an if statement, but it's not as safe/exhaustive as a switch.

You could model your original example with if case statements. Admittedly the if case syntax is a bit strange, but it's already available:

if case 3, 5 = someInt { ... }
else if case 7, 9 = someInt { ... }
else { /* As with any `if` ladder, `else` is optional */ }

Alternatively, in this specific instance you can use normal Boolean predicates and not need pattern matching:

if [3, 5].contains(someInt) { ... }
else if [7, 9].contains(someInt) { ... }
else { /* As with any `if` ladder, `else` is optional */ }

Maybe it's over-bearing or a matter of personal style, but IMO the default being required (or being otherwise exhaustive) on every switch is a plus.

1 Like

Thank you for these tips. In both the examples that you just showed
me, a drawback is that you have to repeat someInt (or something even
longer than that), for each case. This is what the switch statement
so elegantly avoids.

By the way, did you mean to say == someInt in the first example?

I don't consider the switch as being exhaustive when you write
default: break. With enums, and if we go through every possible
value, only then are we allowed to leave out the default.

Consider the if statement. What if it always had to be written like
this to be "exhaustive":

if someBool { ... } else { break } // instead of just: if someBool { ... }

Wouldn't that be a bit redundant?

1 Like

If I understand you correctly, you meant that if a switch doesn't
have a default, it signals to us that it takes care of all possible
cases, at least when we have an enum. Admittedly, my proposed
solution doesn't do that.

It was maybe a bit cryptic when I said in a previous message
that I don't consider the "default: break" to be exhaustive. What
I meant was:

It's as if someone would ask me what I will do tomorrow, and my
answer would be: I will eat lunch at noon – and as for the rest of
the day – I'm not going to say anything about that.

That doesn't feel exhaustive. But, this is more about semantics
than anything else.

Here's another example of the should/be/else, with a different
formatting style:

should someInt be 1 { function1()
} be 2 { function2()
} be 3 { function3()
}

One possible solution for the "signal problem":

If the switch object is an enum with a limited number
of possible values, and not all cases are covered, then
an else block must be included. The else block could
also be left empty:

should someEnum be .a { function1()
} be .b { function2()
} be .c { function3()
} else {
}
1 Like

I didn't. if case and guard case uses a single =. Swift If Case Let

Yep

I don't see that as too much of an issue, personally. I'm happy to either extract a constant, or use a switch.

Apart from default not being empty, isn't this pretty much the existing rules around switch?

1 Like

Thanks for the link. I will have a closer look when I get
the time.

What I and many others don't like about the switch,
is that it behaves differently, being a heritage from
other languages.

With the new approach I don't have to add a break if
the object is an Int, for example.

Here's a little zen – just for fun I entered this in Xcode:

switch someInt {
case ...(-1): Swift.print("negative number")
case 0: Swift.print("zero")
case 1...: Swift.print("positive number")
}

This wasn't allowed, because the "Switch must be exhaustive".

Hmm, it looks exhaustive to me :slight_smile:

The compiler would need to evaluate the range expressions at compile-time and determine that all possible ranges are covered.

2 Likes

That’s a limitation of exhaustiveness-checking logic that happens to be implemented today, which is currently exclusive (heh) to Bool and enums (and compositions of enums), afaik. Stopping there wasn’t an intentional design decision of the language, it was merely the low hanging fruit, and nobody has gone to implement any further.

IMO, The correct solution there is to improve that logic (e.g. to teach the compiler about various kinds of integer ranges), rather than making an alternative syntactic universe as a workaround.

5 Likes

Let me just add another possible solution to my original proposal,
a solution that would solve what I call the "signal problem":

With the should/be/else, let the programmer decide if an else
block should be added or not. Then, introduce in addition to that
a shall/be statement. The shall/be can only be used with enums,
and must always be exhaustive. The shall/be can therefore never
include an else block.

shall someEnum be .gold { function1()
} be .silver { function2()
} be .bronze { function3()
}

Initially I didn't see the need for this suggestion from your first post but after reading through the use cases in the rest of the discussion, I think your proposal can be summarised into these advantages and I'm coming around to liking it:

  • Not needing to repeat the subject being evaluated like you would with an if, else.
  • Able to skip the default branch unlike switch.

Were you inspired by this from other languages? Is the should, be, else format used else where?

2 Likes

Thank you! No, it wasn't that I was inspired by other languages.
One could say that I was inspired by Swift's ordinary curly brace
closures (which, by the way, are also used in other languages),
like the if/else.

Python does not use these curly braces. Now that I check out
what the Python switch looks like, I see that it does use else. I had
forgotten all about that. Maybe I knew about it subconsciously.
The switch in Swift is similar to Python in that there are no
curly braces to embrace a case within the switch; and that gives
it a clean appearance.

I wouldn't expect the words should, shall, and be, to be used
elsewhere, but I haven't checked. I have, however, encountered
a slightly odd trait regarding be:

"case is someClass: ..." sounds ok, but "be is someClass { ... }"
sounds a bit quirky. It could still be logical when read like this:

"should it be, that it is someClass, then ..."

2 Likes