Как вызвать неоднозначную универсальную функцию в Swift?


Я определил две общие функции

func job<T: Comparable>(x: T) {
  println("1")
}

func job<T: Hashable>(x: T) {
  println("2")
}

И когда я пытаюсь вызвать один из них, например:

let myInt: Int = 1 // Explicit Int just for clarity of the example
job(myInt)

Конечно Свифт жалуется и выдает ошибку
неоднозначное использование слова "работа"
это понятно, потому что не ясно, хочу ли я использовать сопоставимый один или Хэшируемый (Int соответствует им обоим)

Есть ли способ подсказать компилятору, какой из них я хочу использовать?

1 8

1 ответ:

Это неоднозначно, потому что Int является одновременно Hashable и Comparable, и ни один из этих двух протоколов не находится в одной иерархии. (Вы можете просмотретьInt иерархия протоколов на 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'

Вы не можете явно указать ему, какую функцию вызывать, из-за связанных требований к типу каждого протокола, но то, что вы можете сделать, это определить третью функцию:

func f<T: Comparable where T: Hashable>(t: T) {
    println("Both Hashable & Comparable: \(t)")
}
f(number)
// Both Hashable & Comparable: 5

Именно так Swift реализует оператор ..<, который в противном случае был бы неоднозначно для типов, реализующих как Comparable, так и ForwardIndexType.


Чтобы расширить немного дальше, вот взгляд на то, что я подразумевал под "вы не можете явно сказать ему, какую функцию вызывать, из-за связанных требований к типу каждого протокола."Протоколы могут использоваться как типы, как описано в главе Swift book о Протоколах :

protocol RandomNumberGenerator {
    func random() -> Double
}

class Dice {
    let generator: RandomNumberGenerator
    // ...
}

В этом примере свойство генератора может быть любого типа, соответствующего RandomNumberGenerator - аналогично тому, как id<ProtocolName> используется в Однако протоколы могут использоваться в качестве типов только, если они не включают в свое объявление ассоциированный тип или ссылку Self. К сожалению, это исключает почти все встроенные типы в Swift, включая Hashable и Comparable.

Hashable наследуется от Equatable, который ссылается на Self при определении оператора ==:

func ==(lhs: Self, rhs: Self) -> Bool

И Comparable делает то же самое со своими операторами:

func <=(lhs: Self, rhs: Self) -> Bool
// similar definitions for <, >, and >=

Эти протоколы могут только использоваться как общие ограничения, и не используется в качестве типа при объявлении переменной. (Насколько я могу судить, это недокументировано, но обнаруживается через сообщения об ошибках.)

Два протокола, которые не имеют это ограничение, являются Printable и BooleanType, поэтому мы можем посмотреть, как они работают. Bool является единственным встроенным типом, который соответствует BooleanType, и это также Printable, так что это будет наш тестовый тип. Вот наши общие функции p() и переменная t - обратите внимание, что, как и раньше, мы не можем просто вызвать функция с 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'

Вместо этого нам нужно бросить (upcast?) t к определенному протоколу, используя ключевое слово as, и вызываем определенную универсальную функцию таким образом:

p(t as Printable)
// Printable: true

p(t as BooleanType)
// BooleanType: true
Таким образом, пока у нас есть квалифицирующий протокол, мы можем выбрать, какой вариант универсального метода вызывать.