swift - 在泛型函数 where 子句中使用 Self 时出错

标签 swift generics swift4 swift-protocols

当我尝试使用 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())

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

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/

相关文章:

ios - 类型 'UIImage' 没有成员 'radialGradientImage'

ios - 使用 queryOrdered(byChild : "name") queryEqual 选择 Firebase 数据

ios - CloudKit 记录的本地缓存

ios - MKMapView 自动缩小动画

ios - 无法在 Firebase 中获取节点的值

swift - 等待WKWebView评估JavaScript完成

swift - 按钮渐变在 ios 12 (Swift 4.2) 后不起作用

Java泛型方法: inconsistency when inferring upper bound on return type from argument type

c# - 为什么 C# 禁止泛型属性类型?

c# - 使用泛型类的参数作为事件类型