Swift Type Erasure尝试:“引用无效的关联类型”

时间:2019-09-26 20:55:53

标签: swift generics swift-protocols type-erasure

我正在做一个虚构的练习,以尝试实现类型擦除的容器。

import Foundation

protocol MoverType {
    func move()
}
extension MoverType {
    func move() { print("\(type(of: self)) is moving") }
}

class Slithering: MoverType {}
class Walking: MoverType {}
class Trotting: MoverType {}

protocol Animal {
    associatedtype Mover: MoverType
    var mover: Mover { get }
}

class Snake: Animal {
    let mover = Slithering()
}
class Dog: Animal {
    let mover = Trotting()
}
class Human: Animal {
    let mover = Walking()
}

class AnyAnimal: Animal {  // ERROR: Type 'AnyAnimal' does not conform to protocol 'Animal'
    var _mover: () -> Mover
    init<A: Animal>(animal: A) where Mover == A.Mover {
        _mover = {
            return animal.mover
        }
    }

    // ERROR HERE: "Reference to invalid associated type "Mover" of of type "AnyAnimal"
    var mover: Mover { fatalError() }
}

let d = AnyAnimal(animal: Dog())
let s = AnyAnimal(animal: Snake())

let animals = [d, s]   // Array<AnyAnimal>
for a in animals {
    a.mover.move()
}

我故意不希望我的AnyAnimal容器成为AnyAnimal<T>容器。因为,我希望能够在Animal中存储许多Array<AnyAnimal>实例。

但是,如您在上面的代码中看到的那样,编译器在抱怨AnyAnimal类。据我了解,Mover的协议要求将由where初始化程序中的通用AnyAnimal子句解决。

请帮助我了解缺失的内容。 (或者,甚至有可能首先创建一个非通用的类型擦除包装器?)

2 个答案:

答案 0 :(得分:2)

您的代码无法编译,因为需要在编译时通过为Mover协议提供具体的实现来解析关联的类型。

您可以做的是同时清除MoverType协议:

struct AnyMover: MoverType {
    private let mover: MoverType

    init(_ mover: MoverType) {
        self.mover = mover
    }

    func move() {
        mover.move()
    }
}

class AnyAnimal: Animal {
    let mover: AnyMover

    init<A: Animal>(animal: A) {
        mover = AnyMover(animal.mover)
    }    
}

答案 1 :(得分:1)

我同意Cristik关于添加AnyMover类型橡皮擦的意见。从字面上看,这就是您要说的方法。但是,如果您走这条路,通常会设计错误的协议。例如,虽然我知道这是捏造的,但这是协议设计错误的一个很好的例子。动物协议几乎可以肯定是:

protocol Animal {
    func move()
}

然后您的associatedtype和相关问题消失了。而且您的界面更有意义。动物可以移动。这不是“与移动者”。我希望这种类型的每次使用都与您的示例animal.mover.move()几乎完全相同。也就是说,mover是调用者不必关心的实现细节。

[Animal]循环中您还能做什么 ?您如何编写使用.mover并没有调用.move的通用代码?没有其他方法。

我知道这是捏造的,但是这种情况恰恰是在许多实际情况下出现的,因此您应该警惕。当您发现自己喜欢类型擦除器时,特别是如果您开始达到两个级别的类型擦除器时,您需要询问您做错了什么。并非总是如此,但是在大多数情况下,对于您而言,有一个更好的解决方案。

顺便说一句,这里的另一种方法是保留Movers,但只要让它们成为协议即可。呼叫者是否真的想知道履带式和滑行式之间的区别?难道不是要隐藏整个目标吗?如果是这样,您可以这样:

protocol Animal {
    var mover: MoverType { get }
}

同样,所有问题都消失了。

无论哪种方法,如果有move()可用,您仍然可以自动实现mover方法。例如,您可以通过以下方式设计协议:

// Something that moves animals
protocol Mover {
    func move(animal: Animal)
}

// Something that has a mover
protocol MoverProviding {
    var mover: Mover { get }
}

// And of course Animals. They might be MoverProviding. They might not.
protocol Animal {
    func move()
}

// But if they *are* MoverProviding, we can use that.
extension Animal where Self: MoverProviding {
    func move() {
        mover.move(animal: self)
    }
}

键入associatedtype时,通常应该想到“该协议就是向其他类型添加额外的算法(方法和函数)。”如果协议的重点是允许异构收集,那么您可能走错了路。类型擦除器有时是有用且重要的,但是如果您觉得自己非常需要它们(尤其是由于异构集合而特别需要它们),则可能存在设计问题。