Swift: nested type erase

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: Transportation {
    typealias Transport = _Transport
    private let box: _AnyTransportationBase
    init(_ 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: _AnyTransportationBase {
    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 : 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] = [AnyTransportation(RedBusLine()), AnyTransportation(BlueBusLine())]
busRides.forEach { print($0.name) } // prints "Red linenBlue 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
, right?

final class AnyVehicle: Vehicle {
    private let box: _AnyVehicleBase
    init(_ concrete: Concrete) {
        box = _AnyVehicleBox(concrete)
    }
    var speed: Int { return box.speed }
}

final class _AnyVehicleBox: _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] = [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]
?

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>] = [BusLine(), FerryLine()] // want to put `BusLine` and `FerryLine` in same array

If you want to express any transportation with any vehicle with any engine, then you want 3 boxes, each talking in terms of the “previous” type-erased wrappers. You don’t want generic placeholders on any of these boxes, as you want to talk in terms of fully heterogenous instances (e.g not any transportation with a specific
Vehicle
type, or any vehicle with a specific
Motor
type).

Furthermore, rather than using a class hierarchy to perform the type erasing, you can use closures instead, which allows you to capture the base instance rather than storing it directly. This allows you to remove a significant amount of the boilerplate from your original code.

For example:

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: 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: 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 = $0 }
    }
}

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

Note that we still built AnyMotor
despite Motor
not having any associated types. This is because protocols don’t conform to themselves, so we cannot use Motor
itself to satisfy the Engine
associated type (that requires : Motor
) – we currently have to build a concrete wrapper type for it.

Hello, buddy!稿源:Hello, buddy! (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 移动开发 » Swift&colon; nested type erase

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录