Swift ranges in their current state include two variations: n...m
, a closed range that includes both n and m, and n..<m
, a half-open range that includes n but stops short of m. I’m working on proposals to introduce a striding(by:) method and enhance range operators for Swift 3.
Last night I was chatting with Nate Cook about the possibility of unbounded ranges, in other words, a range that wasn’t limited on one side or another but that went off towards infinity. You can think of these as a kind of “range ray”, pointing out either up or down the number line.
For example, 5...
is a an unbounded range including all numbers greater than or equal to 5, and 5<..
would be the equivalent open version that didn’t include 5.
Nate recently wrote up a bunch of operators to support working with unbounded ranges but it turns out you don’t really need these if you use some built in number properties to create “unbounded” (the quotes are deliberate) ranges. Let me step back and set things up before jumping into the solution.
The main reason you’d ever want to use unbounded ranges is for pattern matching. You can use the ~= operator, switch statements, if case and guard case to test whether a range contains a value.
Now you might just now be thinking: why use “5<.. ~= x
” when you can just say “if x >= 5
“? The latter is shorter, clearer, and far more obvious. The answer is this case comes down to those pattern matching scenarios, which primarily consist of switch
statements and a few oddball if case/guard case
uses.
The following switch statement provides a case for x >= 5
, but it does so by binding a new variable and then building in a where clause.
switch i { case let x where x >= 5: print(i, "is >= 5") default: print("nope") }
Surely there’s a better way to frame this. And there is. Nearly every number type offers either .min
and .max
static properties or .infinity
, or in the case of CGFloat
, both. (Use a minus sign to get -.infinity
if you need that.)
If you plug that back into the switch statement, you end up with a much more satisfying case:
// Note: a ... b translates to a ..< b + 1 // so a ... .max fails, which is why it is ..< .max here // If you want, you can add an Int.max case *or* // you can use 5 ... .max as ClosedInterval switch i { case 5 ..< .max: print(i, "is >= 5") default: print("nope") }
So you don’t need special operators to create open ranges suitable for pattern matching. You can work with the number constants that already exist:
- Integers:
i ..< .max
and.min ... i
- Doubles:
-.infinity ... f
andf ..< .infinity
Once the range operator vocabulary expands, you’ll be able to reference i <.< .max
and f <.< .infinity
as well, filling in the current gaps in the system.
Comments are closed.