There’s a pattern I find myself using that I’m trying to train myself out of and it goes something like this:
switch something { case some complicated binding case: // ...do stuff with the binding results default: break }
Here’s a real-world example:
enum Tree<T> { case empty indirect case node(value: T, left: Tree<T>, right: Tree<T>) func show(indent: Int = 0) { switch self { case let .node(value: value, left: left, right: right): print(String(repeating: " " as Character, count: indent), value) right.show(indent: indent + 4) left.show(indent: indent + 4) default: break } } }
The reason I’m trying to break myself out of this pattern is that there’s a much simpler way to do this, which is to use if case
instead of switch
. There’s only one case to consider, and the default case does nothing.
Refactoring produces the following simplified code, allowing all the binding and the behavior related to the .node
case to be compressed together.
enum Tree<T> { case empty indirect case node(value: T, left: Tree<T>, right: Tree<T>) func show(indent: Int = 0) { if case let .node(value: v, left: l, right: r) = self { print(String(repeating: " " as Character, count: indent), v) r.show(indent: indent + 4) l.show(indent: indent + 4) } }
I tend to push myself to use switch
and guard
over if
for many cases to enhance intent and readability, but this is one example where it’s really best just to stick with the classics.
9 Comments
You could also use `guard case let .node(…) = self else { return }` to prevent nesting, if you’re not doing anything for the `empty` case. It does increase the amount of code slightly, so it’s a matter of taste 🙂
If no code follows a guard statement, it shouldn’t be a guard statement. They say “leave scope unless the following conditions are met to prepare the scope for the following execution”
I’m not sure I follow – the code that is currently in your if-block would follow the guard statement.
Yeah, my bad. I was thinking the code would be embedded in else. It’s not. Works for me!
My co-worker was so annoyed at having to constantly write switch statements like above he and I were considering creating a proposal on swift evolution. Thankfully we discovered ‘if case’ before we looked stupid.
One issue I have with this style is it’s possible to write ‘if case .empty = value’ and that looks like assignment, without actually having assigned anything. For readability I much prefer to do ‘if value == .empty’, but to do that I have to add Equatable and a custom equality operator. It’s extra work.
So while ‘if case .empty = value’ comes for free, and is more ‘swifty’ I guess, visually it’s a bit of a speed bump when I’m reading code.
You don’t have to make your enum Equatable if you want to use ‘if value == .empty’ ?
Maybe better to train yourself out of the pattern of using a ‘default’ or ‘case _’ option on switch statements.
Let the compiler warn you when there’s a case you haven’t considered – even if it is to explicitly ignore it (eg. case .empty : ()).
That way you can expand your domain later (or maybe someone else can) and you get feedback on places where you might have to consider adding functionality. With the pretty good pattern matching functionality in Swift switch statements it’s good to know you’ve got all bases covered and adding defaults may unknowingly introduce errors.
Let the compiler (ok, I know past history in the Swift compiler hasn’t always been good) take the strain.
Why can’t you/I use simple “if windDirection == .East”??
I make it a rule that always use switch for enums. Because it’s a enum and it’s very likely to add more types in the future and then I will be happy that I can handle all the types easily. There are more code when using switch but the code is more readable. And I prefer readability.