Using Swift 3.0 (I could use Swift 4.0 if that would help me... But I don't think it will) I would like to Type Erase two levels. I what to type erase a protocol having an associatedtype, which conforms to a protocol that in turn itself has an associatedtype. So one could say that I want to type erase nested associatedtypes.
The code below is an extremely simplified version of my code, but it is more clear that way. So what I really want is something like this:
Original Scenario - Unsolved
protocol Motor {
var power: Int { get }
}
protocol Vehicle {
associatedType Engine: Motor
var engine: Engine { get }
}
protocol Transportation {
associatedType Transport: Vehicle
var transport: Transport { get }
}
And then I would like to type erase Transportation
and be able to store an array of AnyTransportation
which could have any Vehicle
which in turn could have any Motor
.
So this is a scenario with 3 protocols, where 2 of them have (nested) associatedtypes.
I do not know how to do this. Actually, I do not even know how to solve the even more simple scenario:
Simplified Scenario - Unsolved
We could simplify the original scenario above to a version where we have 2 protocols, where only 1 of them have an associatedtype:
protocol Vehicle {
var speed: Int { get }
}
protocol Transportation {
associatedtype Transport: Vehicle
var transport: Transport { get }
var name: String { get }
}
Then lets say that we have a Bus
conforming to Vehicle
:
struct Bus: Vehicle {
var speed: Int { return 60 }
}
And then we have two different BusLines, RedBusLine
and BlueBusLine
both conforming to 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
}
}
We can then type erase Transportation
using the base and box pattern and classes, as described by 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") }
}
We can then put either RedBusLine
or BlueBusLine
in
let busRides: [AnyTransportation<Bus>] = [AnyTransportation(RedBusLine()), AnyTransportation(BlueBusLine())]
busRides.forEach { print($0.name) } // prints "Red line\nBlue line"
In the blog post about type erasure linked to above, what I want is actually a workaround for Homogeneous Requirement
.
Imagine we have another Vehicle
, e.g a Ferry
and a FerryLine
:
struct Ferry: Vehicle {
var speed: Int { return 40 }
}
struct FerryLine: Transportation {
let transport: Ferry = Ferry()
var name = "Ferry line"
}
I guess we want to type erase Vehicle
now? Because we want an array of AnyTransportation<AnyVehicle>
, right?
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'
Of course this does not work... because AnyTransportation
expects passing in a type conforming to Transportation
, but AnyVehicle
does not conform to it of course.
But I have not been able to figure out a solution for this. Is there any?
Question 1: Is it possible to type erase the Simple Scenario allowing for: [AnyTransportation<AnyVehicle>]
?
Question 2: If the Simple Scenario is solvable, is the original scenario also solvable?
Below follows only a more detailed explanation of what I want to achieve with the Original Scenario
Original Scenario - Expanded
My original need is to put any Transportation
, having any Vehicle
, that in itself has any Motor
inside the same array:
let transportations: [AnyTransportation<AnyVehicle<AnyMotor>>] = [BusLine(), FerryLine()] // want to put `BusLine` and `FerryLine` in same array