195

I am familiar with switch statements in Swift, but wondering how to replace this piece of code with a switch:

if someVar < 0 {
    // do something
} else if someVar == 0 {
    // do something else
} else if someVar > 0 {
    // etc
}
Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
Pieter
  • 2,621
  • 4
  • 19
  • 26
  • Although this is an interesting question, I think the code using switch is far less readable than the if statements. Just because you can, doesn't mean you should. – Rog Feb 18 '19 at 18:42

10 Answers10

313

Here's one approach. Assuming someVar is an Int or other Comparable, you can optionally assign the operand to a new variable. This lets you scope it however you want using the where keyword:

var someVar = 3

switch someVar {
case let x where x < 0:
    print("x is \(x)")
case let x where x == 0:
    print("x is \(x)")
case let x where x > 0:
    print("x is \(x)")
default:
    print("this is impossible")
}

This can be simplified a bit:

switch someVar {
case _ where someVar < 0:
    print("someVar is \(someVar)")
case 0:
    print("someVar is 0")
case _ where someVar > 0:
    print("someVar is \(someVar)")
default:
    print("this is impossible")
}

You can also avoid the where keyword entirely with range matching:

switch someVar {
case Int.min..<0:
    print("someVar is \(someVar)")
case 0:
    print("someVar is 0")
default:
    print("someVar is \(someVar)")
}
Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
185

With Swift 5, you can choose one of the following switch in order to replace your if statement.


#1 Using switch with PartialRangeFrom and PartialRangeUpTo

let value = 1

switch value {
case 1...:
    print("greater than zero")
case 0:
    print("zero")
case ..<0:
    print("less than zero")
default:
    fatalError()
}

#2 Using switch with ClosedRange and Range

let value = 1

switch value {
case 1 ... Int.max:
    print("greater than zero")
case Int.min ..< 0:
    print("less than zero")
case 0:
    print("zero")
default:
    fatalError()
}

#3 Using switch with where clause

let value = 1

switch value {
case let val where val > 0:
    print("\(val) is greater than zero")
case let val where val == 0:
    print("\(val) is zero")
case let val where val < 0:
    print("\(val) is less than zero")
default:
    fatalError()
}

#4 Using switch with where clause and assignment to _

let value = 1

switch value {
case _ where value > 0:
    print("greater than zero")
case _ where value == 0:
    print("zero")
case _ where value < 0:
    print("less than zero")
default:
    fatalError()
}

#5 Using switch with RangeExpression protocol's ~=(_:_:) operator

let value = 1

switch true {
case 1... ~= value:
    print("greater than zero")
case ..<0 ~= value:
    print("less than zero")
default:
    print("zero")
}

#6 Using switch with Equatable protocol's ~=(_:_:) operator

let value = 1

switch true {
case value > 0:
    print("greater than zero")
case value < 0:
    print("less than zero")
case 0 ~= value:
    print("zero")
default:
    fatalError()
}

#7 Using switch with PartialRangeFrom, PartialRangeUpTo and RangeExpression's contains(_:) method

let value = 1

switch true {
case (1...).contains(value):
    print("greater than zero")
case (..<0).contains(value):
    print("less than zero")
default:
    print("zero")
}
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
  • 1
    why is the defaults case needed in #2? seems flakey that if the rannge is from Int.min to Int.max what is left? – μολὼν.λαβέ Nov 17 '17 at 23:39
  • Wow, nice list of options. Good to know there are a number of ways to do this. – Christopher Pickslay May 02 '18 at 20:49
  • 4
    Good overview but flawed because numbers between 0 and 1 are unaccounted for. `0.1` throws a fatal error because `1...` covers only numbers from 1. So this solution only works if `value` is an `Int` but that is dangerous because if the variable type changes the functionality breaks without any compiler error. – Manuel Aug 31 '18 at 00:59
  • 1
    Your solution does not work correctly for Double type. case 1...: print("greater than zero") Is NOT greater than 0 it is greater or equal to 1. – Vlad Mar 10 '19 at 19:27
22

The switch statement, under the hood, uses the ~= operator. So this:

let x = 2

switch x {
case 1: print(1)
case 2: print(2)
case 3..<5: print(3..<5)
default: break
}

Desugars to this:

if 1          ~= x { print(1) }
else if 2     ~= x { print(2) }
else if 3..<5 ~= x { print(3..<5) }
else {  }

If you look at the standard library reference, it can tell you exactly what the ~= is overloaded to do: included is range-matching, and equating for equatable things. (Not included is enum-case matching, which is a language feature, rather than a function in the std lib)

You'll see that it doesn't match a straight boolean on the left-hand-side. For those kind of comparisons, you need to add a where statement.

Unless... you overload the ~= operator yourself. (This is generally not recommended) One possibility would be something like this:

func ~= <T> (lhs: T -> Bool, rhs: T) -> Bool {
  return lhs(rhs)
}

So that matches a function that returns a boolean on the left to its parameter on the right. Here's the kind of thing you could use it for:

func isEven(n: Int) -> Bool { return n % 2 == 0 }

switch 2 {
case isEven: print("Even!")
default:     print("Odd!")
}

For your case, you might have a statement that looks like this:

switch someVar {
case isNegative: ...
case 0: ...
case isPositive: ...
}

But now you have to define new isNegative and isPositive functions. Unless you overload some more operators...

You can overload normal infix operators to be curried prefix or postfix operators. Here's an example:

postfix operator < {}

postfix func < <T : Comparable>(lhs: T)(_ rhs: T) -> Bool {
  return lhs < rhs
}

This would work like this:

let isGreaterThanFive = 5<

isGreaterThanFive(6) // true
isGreaterThanFive(5) // false

Combine that with the earlier function, and your switch statement can look like this:

switch someVar {
case 0< : print("Bigger than 0")
case 0  : print("0")
default : print("Less than 0")
}

Now, you probably shouldn't use this kind of thing in practice: it's a bit dodgy. You're (probably) better off sticking with the where statement. That said, the switch statement pattern of

switch x {
case negative:
case 0:
case positive:
}

or

switch x {
case lessThan(someNumber):
case someNumber:
case greaterThan(someNumber):
}

Seems common enough for it to be worth considering.

oisdk
  • 9,763
  • 4
  • 18
  • 36
15

You can:

switch true {
case someVar < 0:
    print("less than zero")
case someVar == 0:
    print("eq 0")
default:
    print("otherwise")
}
rintaro
  • 51,423
  • 14
  • 131
  • 139
12

This is how it looks like with ranges

switch average {
case 0..<40: //greater or equal than 0 and less than 40
    return "T"
case 40..<55: //greater or equal than 40 and less than 55
    return "D"
case 55..<70: //greater or equal than 55 and less than 70
    return "P"
case 70..<80: //greater or equal than 70 and less than 80
    return "A"
case 80..<90: //greater or equal than 80 and less than 90
    return "E"
case 90...100: //greater or equal than 90 and less or equal than 100
    return "O"
default:
    return "Z"
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
GOrozco58
  • 1,182
  • 12
  • 10
8

Glad that Swift 4 addresses the problem:

As a workaround in 3 I did:

switch translation.x  {
case  0..<200:
    print(translation.x, slideLimit)
case  -200..<0:
    print(translation.x, slideLimit)
default:
    break
}

Works but not ideal

pkamb
  • 33,281
  • 23
  • 160
  • 191
Jeremy Andrews
  • 807
  • 12
  • 17
6

Since someone has already posted case let x where x < 0: here is an alternative for where someVar is an Int.

switch someVar{
case Int.min...0: // do something
case 0: // do something
default: // do something
}

And here is an alternative for where someVar is a Double:

case -(Double.infinity)...0: // do something
// etc
simons
  • 2,374
  • 2
  • 18
  • 20
5

Swift 5 is nice and clean now

switch array.count {
case 3..<.max: 
    print("Array is greater than or equal to 3")
case .min..<3:
    print("Array is less than 3")
default:
    break
}
EricE
  • 89
  • 1
  • 6
3

The <0 expression doesn't work (anymore?) so I ended up with this:

Swift 3.0:

switch someVar {
    case 0:
        // it's zero
    case 0 ..< .greatestFiniteMagnitude:
        // it's greater than zero
    default:
        // it's less than zero
    }
Dorian Roy
  • 3,094
  • 2
  • 31
  • 51
  • 2
    In swift 3.0, `X_MAX` has been replaced by `.greatestFiniteMagnitude`, ie `Double.greatestFiniteMagnitude`, `CGFloat.greatestFiniteMagnitude` etc. So usually, you can just do `case 0..< .greatestFiniteMagnitude` since the type of `someVar` is already known – Guig Dec 19 '16 at 19:57
  • @Dorian Roy `var timeLeft = 100` `switch timeLeft {case 0...<=7200: print("ok") default:print("nothing") }` Why is the `<=` operator not recognized? If I write it without the equal it works. Thanks – bibscy Jan 10 '17 at 17:06
  • @bibscy You want to use the closed range operator: `case 0...7200:` The operator `<=` is a comparison operator. In a switch you can only use range operators [(see docs)](https://developer.apple.com/reference/swift/1851035-swift_standard_library_operators) – Dorian Roy Jan 10 '17 at 18:58
  • This was great. I was getting this error **expression pattern of type 'Range' cannot match values of type 'Int'** because my `someVar` was an `Int` and I had to do `Double(`someVar)` to make it work... – mfaani Jan 26 '17 at 15:13
2

Cleanest solution I could come up with:

switch someVar {
case ..<0:
    // do something
case 0:
    // do something else
default:
    // etc
}
alex oliveira
  • 491
  • 5
  • 6