Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call ambiguous generic function in Swift?

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?

like image 228
Bartek Chlebek Avatar asked Sep 29 '14 20:09

Bartek Chlebek


1 Answers

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.

like image 96
Nate Cook Avatar answered Oct 04 '22 14:10

Nate Cook