在通用函数where

时间:2017-09-25 12:42:45

标签: swift generics swift4 swift-protocols

当我尝试在作为协议一部分的泛型函数中使用Self作为where子句的一部分时,我遇到了一个问题。

例如,假设我有这个协议并定义了这个通用函数:

protocol Animal {
    associatedtype FoodSource
    func eat(_ food:FoodSource)
}

// The where clause specifies that T2 must conform to
// whatever type is T1's FoodSource associated type 
func feed<T1: Animal, T2>(animal:T1, food:T2) where T2 == T1.FoodSource {
    animal.eat(food)
}

函数Feed使用bracketed语句声明第一个参数必须符合Animal协议。它使用where子句声明第二个参数的类型必须符合第一个参数的关联类型。

可以创建符合此通用功能要求的类,一切都很完美。例如:

protocol Meat {}
protocol Vegetable {}

class Rabbit : Animal {
    typealias FoodSource = Vegetable
    func eat(_ food:FoodSource) {
        print("the Rabbit ate the \(type(of:food))")
    }
}

class Lion : Animal {
    typealias FoodSource = Meat
    func eat(_ food:FoodSource) {
        print("the Lion ate the \(type(of:food))")
    }
}

class Carrot : Vegetable {}
class Steak : Meat {}
class ChickenSalad : Meat, Vegetable {}

// works because Carrot conforms to Vegetable
// prints: "the Rabbit ate the Carrot"
feed(animal: Rabbit(), food: Carrot())

// works because Steak conforms to Meat
// prints: "the Lion ate the Steak"
feed(animal: Lion(), food: Steak())

// works because ChickenSalad conforms to Meat
// prints: "the Lion ate the ChickenSalad"
feed(animal: Lion(), food: ChickenSalad())

// works because ChickenSalad conforms to Vegetable
// prints: "the Rabbit ate the ChickenSalad"
feed(animal: Rabbit(), food: ChickenSalad())

到目前为止一切顺利。

然而,当我在协议中实现相同的泛型模式时,它就不再起作用了:

protocol Food {
    func feed<T:Animal>(to:T) where Self == T.FoodSource
}

extension Food {
    func feed<T:Animal>(to animal:T) where Self == T.FoodSource {
        animal.eat(self)
    }
}

class SteakSalad : Food, Meat, Vegetable {}

SteakSalad().feed(to: Lion())

执行时,此块会引发以下错误:

error: generic parameter 'T' could not be inferred
SteakSalad().feed(to: Lion())
             ^

有没有办法达到预期的行为?

1 个答案:

答案 0 :(得分:2)

在讨论之前,我强烈建议您重新考虑问题并简化类型。一旦你在Swift中混合泛型和协议的漫步,你就会不停地对抗类型系统。部分原因是复杂类型很复杂,即使使用非常强大的类型系统也难以使它们正确。部分原因是Swift没有非常强大的类型系统。与Objective-C或Ruby相比,它确实非常强大,但它在泛型类型方面仍然相当弱,并且有许多概念你无法表达(没有更高级的类型,没有办法表达协方差或逆变,没有依赖类型,并且有像协议这样奇怪的怪癖并不总是符合自己的要求。几乎在我与开发人员合作的复杂类型的每一个案例中,事实证明他们的实际程序并不需要那么多的复杂性。具有相关类型的协议应被视为高级工具;除非你真的需要它们,否则不要触及它们。有关详情,请参阅Beyond Crusty

这不起作用,因为它违反了您的where条款:

func feed<T:Animal>(to:T) where Self == T.FoodSource

因此Animal.FoodSource必须与Self匹配。让我们来看看你如何使用它:

SteakSalad().feed(to: Lion())

因此SelfSteakSaladLion.FoodSourceMeat。这些并不相同,所以这不适用。你真正的意思是:

func feed<T:Animal>(to animal:T) where Self: T.FoodSource

但这不合法Swift(“错误:一致性要求中的第一类'T.FoodSource'不是指泛型参数或相关类型”)。问题是T.FoodSource可能是任何东西;它不一定是一个协议。 “自我符合任意类型”是没有意义的斯威夫特。

我们可以尝试通过使FoodSource至少符合Food来改善这一点,但它会变得更糟:

protocol Food {}
protocol Meat: Food {}

protocol Animal {
    associatedtype FoodSource: Food
}

然后让狮子吃肉:

class Lion : Animal {
    typealias FoodSource = Meat
  

MyPlayground.playground:1:15:注意:可能的匹配'Lion.FoodSource'(又名'肉')不符合'食物'

     

typealias FoodSource =肉

肉不符合食物吗?哈,不。这是Swift中更大的“协议不符合自己”限制的一部分。你不能只像处理继承这样的协议。有时他们这样做,有时他们没有。

你可以做的是肉可以喂给食肉者:

protocol Meat {}

extension Meat {
    func feed<T:Animal>(to animal:T) where T.FoodSource == Meat {
        animal.eat(self)
    }
}

Veg可以喂给吃蔬菜的人:

protocol Vegetable {}

extension Vegetable {
    func feed<T:Animal>(to animal:T) where T.FoodSource == Vegetable {
        animal.eat(self)
    }
}

但我无法通过协议与关联类型(PAT)来实现此通用。这对于Swift类型系统来说太过分了。我的建议是摆脱PAT并使用泛型。大多数问题都会消失。即使在像Scala这样的语言中,它具有更强大的类型系统并且还具有关联类型,正确的答案通常是更简单的泛型(通常甚至不是这样;我们经常在没有需要的时候使事物变得通用)。