Swift:嵌套类型擦除
Swift: Nested type erasure
使用 Swift 3.0(我可以使用 Swift 4.0 如果这对我有帮助...但我不认为它会)我想键入擦除两个级别。我要键入什么擦除具有关联类型的协议,该协议符合本身具有关联类型的协议。所以可以说我想输入 erase nested associatedtypes。
下面的代码是我的代码的一个极其简化的版本,但这样更清晰。所以我真正想要的是这样的:
原始场景 - 未解决
protocol Motor {
var power: Int { get }
}
protocol Vehicle {
associatedType Engine: Motor
var engine: Engine { get }
}
protocol Transportation {
associatedType Transport: Vehicle
var transport: Transport { get }
}
然后我想输入 erase Transportation
并能够存储一个 AnyTransportation
的数组,它可以有任何 Vehicle
,而 Vehicle
又可以有任何 Motor
.
所以这是一个有 3 个协议的场景,其中 2 个有(嵌套)个关联类型。
我不知道该怎么做。其实更简单的场景我都不知道怎么解决:
简化场景 - 未解决
我们可以将上面的原始场景简化为我们有 2 个协议的版本,其中只有 1 个具有关联类型:
protocol Vehicle {
var speed: Int { get }
}
protocol Transportation {
associatedtype Transport: Vehicle
var transport: Transport { get }
var name: String { get }
}
然后假设我们有一个 Bus
符合 Vehicle
:
struct Bus: Vehicle {
var speed: Int { return 60 }
}
然后我们有两条不同的 BusLines、RedBusLine
和 BlueBusLine
都符合 Transportation
struct RedBusLine: Transportation {
let transport: Bus
var name = "Red line"
init(transport: Bus = Bus()) {
self.transport = transport
}
}
struct BlueBusLine: Transportation {
let transport: Bus
var name = "Blue line"
init(transport: Bus = Bus()) {
self.transport = transport
}
}
然后我们可以使用基础和方框模式和 类 键入擦除 Transportation
,如 bignerdranch here:
所述
final class AnyTransportation<_Transport: Vehicle>: Transportation {
typealias Transport = _Transport
private let box: _AnyTransportationBase<Transport>
init<Concrete: Transportation>(_ concrete: Concrete) where Concrete.Transport == Transport {
box = _AnyTransportationBox(concrete)
}
init(transport: Transport) { fatalError("Use type erasing init instead") }
var transport: Transport { return box.transport }
var name: String { return box.name }
}
final class _AnyTransportationBox<Concrete: Transportation>: _AnyTransportationBase<Concrete.Transport> {
private let concrete: Concrete
init(_ concrete: Concrete) { self.concrete = concrete; super.init() }
required init(transport: Transport) { fatalError("Use type erasing init instead") }
override var transport: Transport { return concrete.transport }
override var name: String {return concrete.name }
}
class _AnyTransportationBase<_Transport: Vehicle> : Transportation {
typealias Transport = _Transport
init() { if type(of: self) == _AnyTransportationBase.self { fatalError("Use Box class") } }
required init(transport: Transport) { fatalError("Use type erasing init instead") }
var transport: Transport { fatalError("abstract") }
var name: String { fatalError("abstract") }
}
然后我们可以将 RedBusLine
或 BlueBusLine
放入
let busRides: [AnyTransportation<Bus>] = [AnyTransportation(RedBusLine()), AnyTransportation(BlueBusLine())]
busRides.forEach { print([=16=].name) } // prints "Red line\nBlue line"
在上面链接的关于类型擦除的博客 post 中,我想要的实际上是 Homogeneous Requirement
.
的解决方法
假设我们有另一个 Vehicle
,例如 Ferry
和 FerryLine
:
struct Ferry: Vehicle {
var speed: Int { return 40 }
}
struct FerryLine: Transportation {
let transport: Ferry = Ferry()
var name = "Ferry line"
}
我想我们现在要输入 erase Vehicle
?因为我们想要一个AnyTransportation<AnyVehicle>
的数组,对吧?
final class AnyVehicle: Vehicle {
private let box: _AnyVehicleBase
init<Concrete: Vehicle>(_ concrete: Concrete) {
box = _AnyVehicleBox(concrete)
}
var speed: Int { return box.speed }
}
final class _AnyVehicleBox<Concrete: Vehicle>: _AnyVehicleBase {
private let concrete: Concrete
init(_ concrete: Concrete) { self.concrete = concrete; super.init() }
override var speed: Int { return concrete.speed }
}
class _AnyVehicleBase: Vehicle {
init() { if type(of: self) == _AnyVehicleBase.self { fatalError("Use Box class") } }
var speed: Int { fatalError("abstract") }
}
// THIS DOES NOT WORK
let rides: [AnyTransportation<AnyVehicle>] = [AnyTransportation(AnyVehicle(RedBusLine())), AnyTransportation(AnyVehicle(FerryLine()))] // COMPILE ERROR: error: argument type 'RedBusLine' does not conform to expected type 'Vehicle'
当然这不行...因为AnyTransportation
期望传入一个符合Transportation
的类型,但是AnyVehicle
当然不符合[=46] =]
但是我还没有找到解决办法。有吗?
问题 1:是否可以键入擦除允许的简单场景:[AnyTransportation<AnyVehicle>]
?
问题2:如果Simple Scenario是可解的,那么原场景是否也可解?
下面只是对我想用原始场景
实现的更详细的解释
原始场景 - 扩展
我最初的需求是将任何 Transportation
、具有任何 Vehicle
、本身具有任何 Motor
的任何 Transportation
放入同一个数组中:
let transportations: [AnyTransportation<AnyVehicle<AnyMotor>>] = [BusLine(), FerryLine()] // want to put `BusLine` and `FerryLine` in same array
如果你想用任何带有任何引擎的车辆表达任何运输,那么你需要 3 个盒子,每个盒子都用 "previous" 类型擦除的包装纸来表达。您不希望在这些框中的任何一个上使用通用占位符,因为您想根据完全异构的实例进行讨论(例如,没有任何具有 specific Vehicle
类型的交通工具,或者特定 Motor
类型的任何车辆)。
此外,您可以使用闭包代替 class 层次结构来执行类型擦除,这样您就可以捕获基础实例而不是直接存储它。这使您可以从原始代码中删除大量样板文件。
例如:
protocol Motor {
var power: Int { get }
}
protocol Vehicle {
associatedtype Engine : Motor
var engine: Engine { get }
}
protocol Transportation {
associatedtype Transport : Vehicle
var transport: Transport { get }
var name: String { get set }
}
// we need the concrete AnyMotor wrapper, as Motor is not a type that conforms to Motor
// (as protocols don't conform to themselves).
struct AnyMotor : Motor {
// we can store base directly, as Motor has no associated types.
private let base: Motor
// protocol requirement just forwards onto the base.
var power: Int { return base.power }
init(_ base: Motor) {
self.base = base
}
}
struct AnyVehicle : Vehicle {
// we cannot directly store base (as Vehicle has an associated type).
// however we can *capture* base in a closure that returns the value of the property,
// wrapped in its type eraser.
private let _getEngine: () -> AnyMotor
var engine: AnyMotor { return _getEngine() }
init<Base : Vehicle>(_ base: Base) {
self._getEngine = { AnyMotor(base.engine) }
}
}
struct AnyTransportation : Transportation {
private let _getTransport: () -> AnyVehicle
private let _getName: () -> String
private let _setName: (String) -> Void
var transport: AnyVehicle { return _getTransport() }
var name: String {
get { return _getName() }
set { _setName(newValue) }
}
init<Base : Transportation>(_ base: Base) {
// similar pattern as above, just multiple stored closures.
// however in this case, as we have a mutable protocol requirement,
// we first create a mutable copy of base, then have all closures capture
// this mutable variable.
var base = base
self._getTransport = { AnyVehicle(base.transport) }
self._getName = { base.name }
self._setName = { base.name = [=10=] }
}
}
struct PetrolEngine : Motor {
var power: Int
}
struct Ferry: Vehicle {
var engine = PetrolEngine(power: 100)
}
struct FerryLine: Transportation {
let transport = Ferry()
var name = "Ferry line"
}
var anyTransportation = AnyTransportation(FerryLine())
print(anyTransportation.name) // Ferry line
print(anyTransportation.transport.engine.power) // 100
anyTransportation.name = "Foo bar ferries"
print(anyTransportation.name) // Foo bar ferries
请注意,尽管 Motor
没有任何关联类型,我们仍然构建了 AnyMotor
。这是因为 ,所以我们不能使用 Motor
本身来满足 Engine
关联类型(需要 : Motor
)——我们目前必须为它构建一个具体的包装类型.
Hamish 的解决方案绝对是完成您所要求的正确方法,但是当您进行这么多类型的擦除时,您需要问自己一些问题。
让我们从最后开始:
let transportations: [AnyTransportation<AnyVehicle<AnyMotor>>] = [BusLine(), FerryLine()] // want to put `BusLine` and `FerryLine` in same array
您可以用 transportations
做什么?说真的,你会写什么代码来迭代它而不做 as?
检查?唯一可用的通用方法是 name
。你真的不能调用任何其他东西,因为类型在编译时会不匹配。
这与我的 Beyond Crusty 演讲中的示例非常接近,我认为您应该到同一个地方寻找解决方案。例如,而不是这个:
struct RedBusLine: Transportation {
let transport: Bus
var name = "Red line"
init(transport: Bus = Bus()) {
self.transport = transport
}
}
考虑看起来像这样的解决方案(即没有协议,所有 PAT 问题都消失了):
let redBusLine = Transportation(name: "Red line",
transport: Vehicle(name: "Bus",
motor: Motor(power: 100))
接下来,认真考虑一下您的意思是 Bus
是不是一个结构体。两个相同属性的总线是同一个总线吗?
let red = Bus()
let blue = Bus()
红色和蓝色是同一辆公交车吗?如果不是,则这不是值类型。这是一个引用类型,应该是 class。许多 Swift 讨论将我们推向协议并让我们对 classes 感到羞耻,但 Swift 的实际设计鼓励恰恰相反。确保避免使用 classes,因为它们是真正的值类型,而不仅仅是出于同辈压力。不要仅仅因为它是 Swift 就使用协议。我发现 PAT 是满足非常特殊需求(如 Collection)的工具,而不是大多数问题的首选解决方案。 (直到 Swift 4,甚至 Collection 都是一团糟的协议。)
使用 Swift 3.0(我可以使用 Swift 4.0 如果这对我有帮助...但我不认为它会)我想键入擦除两个级别。我要键入什么擦除具有关联类型的协议,该协议符合本身具有关联类型的协议。所以可以说我想输入 erase nested associatedtypes。
下面的代码是我的代码的一个极其简化的版本,但这样更清晰。所以我真正想要的是这样的:
原始场景 - 未解决
protocol Motor {
var power: Int { get }
}
protocol Vehicle {
associatedType Engine: Motor
var engine: Engine { get }
}
protocol Transportation {
associatedType Transport: Vehicle
var transport: Transport { get }
}
然后我想输入 erase Transportation
并能够存储一个 AnyTransportation
的数组,它可以有任何 Vehicle
,而 Vehicle
又可以有任何 Motor
.
所以这是一个有 3 个协议的场景,其中 2 个有(嵌套)个关联类型。
我不知道该怎么做。其实更简单的场景我都不知道怎么解决:
简化场景 - 未解决
我们可以将上面的原始场景简化为我们有 2 个协议的版本,其中只有 1 个具有关联类型:
protocol Vehicle {
var speed: Int { get }
}
protocol Transportation {
associatedtype Transport: Vehicle
var transport: Transport { get }
var name: String { get }
}
然后假设我们有一个 Bus
符合 Vehicle
:
struct Bus: Vehicle {
var speed: Int { return 60 }
}
然后我们有两条不同的 BusLines、RedBusLine
和 BlueBusLine
都符合 Transportation
struct RedBusLine: Transportation {
let transport: Bus
var name = "Red line"
init(transport: Bus = Bus()) {
self.transport = transport
}
}
struct BlueBusLine: Transportation {
let transport: Bus
var name = "Blue line"
init(transport: Bus = Bus()) {
self.transport = transport
}
}
然后我们可以使用基础和方框模式和 类 键入擦除 Transportation
,如 bignerdranch here:
final class AnyTransportation<_Transport: Vehicle>: Transportation {
typealias Transport = _Transport
private let box: _AnyTransportationBase<Transport>
init<Concrete: Transportation>(_ concrete: Concrete) where Concrete.Transport == Transport {
box = _AnyTransportationBox(concrete)
}
init(transport: Transport) { fatalError("Use type erasing init instead") }
var transport: Transport { return box.transport }
var name: String { return box.name }
}
final class _AnyTransportationBox<Concrete: Transportation>: _AnyTransportationBase<Concrete.Transport> {
private let concrete: Concrete
init(_ concrete: Concrete) { self.concrete = concrete; super.init() }
required init(transport: Transport) { fatalError("Use type erasing init instead") }
override var transport: Transport { return concrete.transport }
override var name: String {return concrete.name }
}
class _AnyTransportationBase<_Transport: Vehicle> : Transportation {
typealias Transport = _Transport
init() { if type(of: self) == _AnyTransportationBase.self { fatalError("Use Box class") } }
required init(transport: Transport) { fatalError("Use type erasing init instead") }
var transport: Transport { fatalError("abstract") }
var name: String { fatalError("abstract") }
}
然后我们可以将 RedBusLine
或 BlueBusLine
放入
let busRides: [AnyTransportation<Bus>] = [AnyTransportation(RedBusLine()), AnyTransportation(BlueBusLine())]
busRides.forEach { print([=16=].name) } // prints "Red line\nBlue line"
在上面链接的关于类型擦除的博客 post 中,我想要的实际上是 Homogeneous Requirement
.
假设我们有另一个 Vehicle
,例如 Ferry
和 FerryLine
:
struct Ferry: Vehicle {
var speed: Int { return 40 }
}
struct FerryLine: Transportation {
let transport: Ferry = Ferry()
var name = "Ferry line"
}
我想我们现在要输入 erase Vehicle
?因为我们想要一个AnyTransportation<AnyVehicle>
的数组,对吧?
final class AnyVehicle: Vehicle {
private let box: _AnyVehicleBase
init<Concrete: Vehicle>(_ concrete: Concrete) {
box = _AnyVehicleBox(concrete)
}
var speed: Int { return box.speed }
}
final class _AnyVehicleBox<Concrete: Vehicle>: _AnyVehicleBase {
private let concrete: Concrete
init(_ concrete: Concrete) { self.concrete = concrete; super.init() }
override var speed: Int { return concrete.speed }
}
class _AnyVehicleBase: Vehicle {
init() { if type(of: self) == _AnyVehicleBase.self { fatalError("Use Box class") } }
var speed: Int { fatalError("abstract") }
}
// THIS DOES NOT WORK
let rides: [AnyTransportation<AnyVehicle>] = [AnyTransportation(AnyVehicle(RedBusLine())), AnyTransportation(AnyVehicle(FerryLine()))] // COMPILE ERROR: error: argument type 'RedBusLine' does not conform to expected type 'Vehicle'
当然这不行...因为AnyTransportation
期望传入一个符合Transportation
的类型,但是AnyVehicle
当然不符合[=46] =]
但是我还没有找到解决办法。有吗?
问题 1:是否可以键入擦除允许的简单场景:[AnyTransportation<AnyVehicle>]
?
问题2:如果Simple Scenario是可解的,那么原场景是否也可解?
下面只是对我想用原始场景
实现的更详细的解释原始场景 - 扩展
我最初的需求是将任何 Transportation
、具有任何 Vehicle
、本身具有任何 Motor
的任何 Transportation
放入同一个数组中:
let transportations: [AnyTransportation<AnyVehicle<AnyMotor>>] = [BusLine(), FerryLine()] // want to put `BusLine` and `FerryLine` in same array
如果你想用任何带有任何引擎的车辆表达任何运输,那么你需要 3 个盒子,每个盒子都用 "previous" 类型擦除的包装纸来表达。您不希望在这些框中的任何一个上使用通用占位符,因为您想根据完全异构的实例进行讨论(例如,没有任何具有 specific Vehicle
类型的交通工具,或者特定 Motor
类型的任何车辆)。
此外,您可以使用闭包代替 class 层次结构来执行类型擦除,这样您就可以捕获基础实例而不是直接存储它。这使您可以从原始代码中删除大量样板文件。
例如:
protocol Motor {
var power: Int { get }
}
protocol Vehicle {
associatedtype Engine : Motor
var engine: Engine { get }
}
protocol Transportation {
associatedtype Transport : Vehicle
var transport: Transport { get }
var name: String { get set }
}
// we need the concrete AnyMotor wrapper, as Motor is not a type that conforms to Motor
// (as protocols don't conform to themselves).
struct AnyMotor : Motor {
// we can store base directly, as Motor has no associated types.
private let base: Motor
// protocol requirement just forwards onto the base.
var power: Int { return base.power }
init(_ base: Motor) {
self.base = base
}
}
struct AnyVehicle : Vehicle {
// we cannot directly store base (as Vehicle has an associated type).
// however we can *capture* base in a closure that returns the value of the property,
// wrapped in its type eraser.
private let _getEngine: () -> AnyMotor
var engine: AnyMotor { return _getEngine() }
init<Base : Vehicle>(_ base: Base) {
self._getEngine = { AnyMotor(base.engine) }
}
}
struct AnyTransportation : Transportation {
private let _getTransport: () -> AnyVehicle
private let _getName: () -> String
private let _setName: (String) -> Void
var transport: AnyVehicle { return _getTransport() }
var name: String {
get { return _getName() }
set { _setName(newValue) }
}
init<Base : Transportation>(_ base: Base) {
// similar pattern as above, just multiple stored closures.
// however in this case, as we have a mutable protocol requirement,
// we first create a mutable copy of base, then have all closures capture
// this mutable variable.
var base = base
self._getTransport = { AnyVehicle(base.transport) }
self._getName = { base.name }
self._setName = { base.name = [=10=] }
}
}
struct PetrolEngine : Motor {
var power: Int
}
struct Ferry: Vehicle {
var engine = PetrolEngine(power: 100)
}
struct FerryLine: Transportation {
let transport = Ferry()
var name = "Ferry line"
}
var anyTransportation = AnyTransportation(FerryLine())
print(anyTransportation.name) // Ferry line
print(anyTransportation.transport.engine.power) // 100
anyTransportation.name = "Foo bar ferries"
print(anyTransportation.name) // Foo bar ferries
请注意,尽管 Motor
没有任何关联类型,我们仍然构建了 AnyMotor
。这是因为 Motor
本身来满足 Engine
关联类型(需要 : Motor
)——我们目前必须为它构建一个具体的包装类型.
Hamish 的解决方案绝对是完成您所要求的正确方法,但是当您进行这么多类型的擦除时,您需要问自己一些问题。
让我们从最后开始:
let transportations: [AnyTransportation<AnyVehicle<AnyMotor>>] = [BusLine(), FerryLine()] // want to put `BusLine` and `FerryLine` in same array
您可以用 transportations
做什么?说真的,你会写什么代码来迭代它而不做 as?
检查?唯一可用的通用方法是 name
。你真的不能调用任何其他东西,因为类型在编译时会不匹配。
这与我的 Beyond Crusty 演讲中的示例非常接近,我认为您应该到同一个地方寻找解决方案。例如,而不是这个:
struct RedBusLine: Transportation {
let transport: Bus
var name = "Red line"
init(transport: Bus = Bus()) {
self.transport = transport
}
}
考虑看起来像这样的解决方案(即没有协议,所有 PAT 问题都消失了):
let redBusLine = Transportation(name: "Red line",
transport: Vehicle(name: "Bus",
motor: Motor(power: 100))
接下来,认真考虑一下您的意思是 Bus
是不是一个结构体。两个相同属性的总线是同一个总线吗?
let red = Bus()
let blue = Bus()
红色和蓝色是同一辆公交车吗?如果不是,则这不是值类型。这是一个引用类型,应该是 class。许多 Swift 讨论将我们推向协议并让我们对 classes 感到羞耻,但 Swift 的实际设计鼓励恰恰相反。确保避免使用 classes,因为它们是真正的值类型,而不仅仅是出于同辈压力。不要仅仅因为它是 Swift 就使用协议。我发现 PAT 是满足非常特殊需求(如 Collection)的工具,而不是大多数问题的首选解决方案。 (直到 Swift 4,甚至 Collection 都是一团糟的协议。)