I've defined two generic functions
func job<T: Comparable>(x: T) {
println("1")
}
func job<T: Hashable>(x: T) {
println("2")
}
and when I try to call one of them, for example with:
let myInt: Int = 1 // Explicit Int just for clarity of the example
job(myInt)
of course Swift complains and throws an error
Ambiguous use of 'job'
which is understandable, because it is not clear whether I want to use the Comparable one or Hashable (Int conforms to both of them)
Is there a way I can hint the compiler which one I want to use?
This is ambiguous because Int
is both Hashable
and Comparable
, and neither of those two protocols are in the same hierarchy. (You can view the Int
protocol hierarchy on Swifter.)
func f<T: Hashable>(t: T) {
println("Hashable: \(t)")
}
func f<T: Comparable>(t: T) {
println("Comparable: \(t)")
}
let number = 5
f(number)
// error: ambiguous use of 'f'
You can't explicitly tell it which function to call, because of the associated type requirements of each protocol, but what you can do is define a third function:
func f<T: Comparable where T: Hashable>(t: T) {
println("Both Hashable & Comparable: \(t)")
}
f(number)
// Both Hashable & Comparable: 5
This is how Swift implements the ..<
operator, which otherwise would be ambiguous for types that implement both Comparable
and ForwardIndexType
.
To expand a little further, here's a look at what I meant by "you can't explicitly tell it which function to call, because of the associated type requirements of each protocol." Protocols can be used as types, as described in the Swift book chapter on Protocols:
protocol RandomNumberGenerator {
func random() -> Double
}
class Dice {
let generator: RandomNumberGenerator
// ...
}
In this example, the generator property can be any type that conforms to RandomNumberGenerator
- similar to how id<ProtocolName>
is used in Objective-C. However, protocols can only be used as types when they do not include an associated type or reference Self
in their declaration. This unfortunately excludes nearly every built-in type in Swift, including Hashable
and Comparable
.
Hashable
inherits from Equatable
, which references Self
when defining the ==
operator:
func ==(lhs: Self, rhs: Self) -> Bool
and Comparable
does the same with its operators:
func <=(lhs: Self, rhs: Self) -> Bool
// similar definitions for <, >, and >=
These protocols can only be used as generic constraints, and not used as a type when declaring a variable. (As far as I can tell this is undocumented, but discoverable through error messages.)
Two protocols that don't have that restriction are Printable
and BooleanType
, so we can look at how they work. Bool
is the only built-in type that conforms to BooleanType
, and it is also Printable
, so that will be our test type. Here are our generic functions p()
and the variable t
- note that, as before, we can't just call the function with t
:
func p<T: Printable>(t: T) {
println("Printable: \(t)")
}
func p<T: BooleanType>(t: T) {
println("BooleanType: \(t)")
}
let t: Bool = true
p(t)
// error: Ambiguous use of 'p'
Instead, we need to cast (upcast?) t
to a particular protocol using the as
keyword, and call a particular generic function that way:
p(t as Printable)
// Printable: true
p(t as BooleanType)
// BooleanType: true
So as long as we have a qualifying protocol, we can choose which variant of the generic method to call.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With