在通用函数 where 子句中使用 Self 时出错
Error using Self in generic function where clause
当我尝试将 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使用括号语句声明第一个参数必须符合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())
^
有什么方法可以达到预期的效果吗?
在讨论这个之前,我强烈建议您重新考虑您的问题并简化您的类型。一旦你在 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())
所以 Self
是 SteakSalad
而 Lion.FoodSource
是 Meat
。那些不相等,所以这不适用。你真正的意思是:
func feed<T:Animal>(to animal:T) where Self: T.FoodSource
但这不合法 Swift ("error: first type 'T.FoodSource' in conformance requirement does not refer to a generic parameter or associated type")。问题是 T.FoodSource
可以是任何东西;它不一定是协议。 "Self conforms to an arbitrary type" 没有意义 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
Meat 不符合 Food 吗?哈,不。这是 Swift 中更大 "protocols don't conform to themselves" 限制的一部分。您不能像对待具有继承性的协议那样对待协议。有时会,有时不会。
你能做的就是把肉喂给肉食者:
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)
}
}
但我不知道如何通过关联类型协议 (PAT) 使这种通用化。这对于 Swift 类型系统来说太多了。我的建议是摆脱 PAT,只使用泛型。大多数这些问题都会消失。即使在像 Scala 这样具有更强大的类型系统和关联类型的语言中,正确的答案通常是更简单的泛型(通常甚至不是那样;我们经常在不需要的时候使事情变得泛型)。
当我尝试将 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使用括号语句声明第一个参数必须符合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())
^
有什么方法可以达到预期的效果吗?
在讨论这个之前,我强烈建议您重新考虑您的问题并简化您的类型。一旦你在 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())
所以 Self
是 SteakSalad
而 Lion.FoodSource
是 Meat
。那些不相等,所以这不适用。你真正的意思是:
func feed<T:Animal>(to animal:T) where Self: T.FoodSource
但这不合法 Swift ("error: first type 'T.FoodSource' in conformance requirement does not refer to a generic parameter or associated type")。问题是 T.FoodSource
可以是任何东西;它不一定是协议。 "Self conforms to an arbitrary type" 没有意义 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
Meat 不符合 Food 吗?哈,不。这是 Swift 中更大 "protocols don't conform to themselves" 限制的一部分。您不能像对待具有继承性的协议那样对待协议。有时会,有时不会。
你能做的就是把肉喂给肉食者:
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)
}
}
但我不知道如何通过关联类型协议 (PAT) 使这种通用化。这对于 Swift 类型系统来说太多了。我的建议是摆脱 PAT,只使用泛型。大多数这些问题都会消失。即使在像 Scala 这样具有更强大的类型系统和关联类型的语言中,正确的答案通常是更简单的泛型(通常甚至不是那样;我们经常在不需要的时候使事情变得泛型)。