当我尝试使用 Self
作为协议(protocol)一部分的通用函数中 where
子句的一部分时,我遇到了问题。
例如,假设我定义了这个协议(protocol)和通用函数:
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 使用括号语句来声明第一个参数必须符合 Animal
协议(protocol)。它使用 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)的一部分时,它不再起作用:
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())
执行时,该 block 会抛出以下错误:
error: generic parameter 'T' could not be inferred
SteakSalad().feed(to: Lion())
^
有什么方法可以实现所需的行为吗?
最佳答案
在讨论这个问题之前,我强烈建议您重新思考您的问题并简化您的类型。一旦你在 Swift 中走上了混合泛型和协议(protocol)的道路,你就会不停地与类型系统作斗争。部分原因是复杂类型很复杂,即使使用非常强大的类型系统也很难使其正确。部分原因是 Swift 没有非常强大的类型系统。当然,与 Objective-C 或 Ruby 相比,它非常强大,但它在泛型类型方面仍然相当薄弱,并且有许多概念无法表达(没有更高种类的类型,无法表达协变或逆变) ,没有依赖类型,并且存在一些奇怪的怪癖,例如协议(protocol)并不总是符合自身)。在我与开发人员合作处理复杂类型的几乎所有情况下,事实证明他们的实际程序并不需要如此复杂。具有相关类型的协议(protocol)应被视为高级工具;除非您确实需要它们,否则不要伸手去拿它们。请参阅Beyond Crusty了解更多信息。
这不起作用,因为它违反了您的 where
子句:
func feed<T:Animal>(to:T) where Self == T.FoodSource
因此 Animal.FoodSource
必须匹配 Self
。让我们看看如何使用它:
SteakSalad().feed(to: Lion())
因此,Self
是 SteakSalad
,Lion.FoodSource
是 Meat
。这些不相等,所以这不适用。你真正的意思是:
func feed<T:Animal>(to animal:T) where Self: T.FoodSource
但这在 Swift 中是不合法的(“错误:符合要求的第一个类型‘T.FoodSource’不引用泛型参数或关联类型”)。问题是 T.FoodSource
可以是任何东西;它不一定是一个协议(protocol)。 “ self 符合任意类型”在 Swift 中没有意义。
我们可以尝试通过使 FoodSource
至少符合 Food
来改进这一点,但情况会变得更糟:
protocol Food {}
protocol Meat: Food {}
protocol Animal {
associatedtype FoodSource: Food
}
然后让狮子吃肉:
class Lion : Animal {
typealias FoodSource = Meat
MyPlayground.playground:1:15: note: possibly intended match 'Lion.FoodSource' (aka 'Meat') does not conform to 'Food'
typealias FoodSource = Meat
肉不符合食物吗?哈,不。这是 Swift 中更大的“协议(protocol)不符合自身”限制的一部分。您不能仅仅将协议(protocol)视为具有继承性。有时他们这样做,有时他们不这样做。
你能做的就是将肉喂给肉食者:
protocol Meat {}
extension Meat {
func feed<T:Animal>(to animal:T) where T.FoodSource == Meat {
animal.eat(self)
}
}
蔬菜可以喂给素食者:
protocol Vegetable {}
extension Vegetable {
func feed<T:Animal>(to animal:T) where T.FoodSource == Vegetable {
animal.eat(self)
}
}
但据我所知,没有办法使这个通用的协议(protocol)与关联类型(PAT)。对于 Swift 类型系统来说这实在是太多了。我的建议是摆脱 PAT,只使用泛型。这些问题大部分都会消失。即使在像 Scala 这样具有更强大的类型系统并且还具有关联类型的语言中,正确的答案通常是更简单的泛型(而且通常甚至不是这样;我们经常在不需要时使事物变得泛型)。
关于swift - 在泛型函数 where 子句中使用 Self 时出错,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46405548/