ClosedInterval
is shy. You have to coax it out from behind its friend, Range
.
// r will be of type Range<Int> let r = 1...5 // if you want a ClosedInterval<Int>, you // have to be explicit: let integer_interval: ClosedInterval = 1...5 // if you use doubles though, you don’t need // to declare the type explicitly. // floatingpoint_interval is a ClosedInterval<Double> let floatingpoint_interval = 1.0...5.0
Ever since Swift 1.0 beta 5, Range
has supposed to be only for representing collection index ranges. If you’re not operating on indices, ClosedInterval
is probably what you want. It has methods like contains
, which determines in constant time if an interval contains a value. Range can only use the non-member contains
algorithm, which will turn your range into a sequence and iterate over it – don’t accidentally put a loop in your loop!
But Range
is not going quietly. If you use the ...
operator with an integer, it elbows ClosedRange
out the way and dashes onto the stage. Why? Because integers are forward indexes (as used by Array
), and the Swift ...
operator has 3 declarations:
func ...<T : Comparable>(start: T, end: T) -> ClosedInterval<T> func ...<Pos : ForwardIndexType>(minimum: Pos, maximum: Pos) -> Range<Pos> func ...<Pos : ForwardIndexType where Pos : Comparable>(start: Pos, end: Pos) -> Range<Pos>
When you write let r = 1...5
, Swift calls the last one of these, and you get back a Range
. To understand why, we need to run through the different ways Swift decides which overloaded function to call. There are a lot of them.
Let’s start with:
Differing Return Values
In Swift, you can’t declare the exact same function twice:
func f() { } // error: Invalid redeclaration of f() func f() { }
What you can do in Swift, unlike in some other languages, is define two versions of a function that differ only by their return type:
struct A { } struct B { } func f() -> A { return A() } // this second f is fine, even though it only differs // by what it returns: func f() -> B { return B() } // note, typealiases are just aliases not different types, // so you can't do this: typealias L = A // error: Invalid redeclaration of 'f()' func f() -> L { return A() }
When calling these only-differing-by-return-value functions, you need to give Swift enough information at the call site for it to unambiguously determine which one to call:
// error: Ambiguous use of 'f' let x = f() // instead you must specify which return value you want: let x: A = f() // calls the A-returning version // x is of type A // or if you prefer this syntax: let y = f() as B // calls the B-returning version // y is of type B // or, maybe you're passing it in as the argument to a function: func takesA(a: A) { } // the A-returning version of f will be called takesA(f())
Finally, if you want declare a reference to a function f
, you need to specify the full type of the function. Note, once you have assigned it, the reference is to a specific function. It doesn’t need further disambiguation:
// g will be a reference to the A-returning version let g: ()->A = f // h will be a reference to the B-returning version let h = f as ()->B // calling g doesn't need further info, it references // a specific f, and z will be of type A: let z = g()
OK, so ...
is a function that differs by return type – it returns either a ClosedInterval
or a Range
. By explicitly specifying the result needs to be a ClosedInterval
, we can force Swift to call the function that returns one.
But if we don’t specify which ...
explicitly, we don’t get an error about ambiguity as seen above. Instead, Swift picks the Range
version by default. How come?
Because the different versions of ...
also differ by the arguments they can take. In the next article, we’ll take a look at how that works.
[…] the previous article, we saw how Swift allows overloading by just the return type. With these kind of overloads, […]
[…] we looked at how Swift functions can be overloaded just by return type, and how Swift picks between different possible overloads based on a best-match […]
[…] is part 4 of a series on how overloaded functions in Swift are chosen. Part 1 covered overloading by return type, part 2 was about how different functions with simple single […]
[…] fact, because of Swift’s operator overloading, you can even give it the same […]
[…] of a series of posts on how Swift resolves ambiguity in overloaded functions. You can find Part 1 here. In the previous article, we looked at how generics are […]
[…] в “перегруженных” функциях. Часть 1 можно найти здесь (русский перевод – здесь). В предыдущей статье […]
[…] of a series of posts on how Swift resolves ambiguity in overloaded functions. You can find Part 1 here. In the previous article, we looked at why you get a Range from the … by default. The answer? […]
[…] talked about overload priority a long time ago, and it still pertains even with protocol extensions. The most specific function […]