Swift: Nested type erasure

Viewed 816

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
2 Answers
Related