Swift: erasing a nested type

Using Swift 3.0 (I could use Swift 4.0 if it helped me ... But I don't think it will) I would like to type Erase two levels. I, what to type, erases a protocol with an associated type, which corresponds to the protocol, which in turn has an associated type. Thus, it can be said that I want to print the erasure of nested associated types.

The code below is an extremely simplified version of my code, but it is clearer. So what I really want is something like this:

Original Screenplay - Undisclosed

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 AnyTransportation

that anyone can have Vehicle

, which, in turn, anyone can have Motor

.

So this is a scenario with 3 protocols where 2 of them have (nested) related types.

I do not know how to do that. In fact, I don't even know how to solve an even simpler scenario:

Simplified scenario - unresolved

We could simplify the original scenario above to a version where we have 2 protocols where only one of them has an associated type:

protocol Vehicle {
    var speed: Int { get }
}

protocol Transportation {
    associatedtype Transport: Vehicle
    var transport: Transport { get }
    var name: String { get }
}

      

Then let's say what matches Vehicle

:

struct Bus: Vehicle {
    var speed: Int { return 60 }
}

      

And then we have two different BusLines, RedBusLine

and BlueBusLine

both correspondTransportation

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 template 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") }
}

      

Then we can 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 type erasure blog post linked to above, what I want is actually a workaround for Homogeneous Requirement

.

Suppose we have another one Vehicle

like 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'm assuming we want to type erase Vehicle

now? Since we want an array 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 doesn't work ... because it AnyTransportation

expects to pass in a type that matches Transportation

, but AnyVehicle

doesn't match of course.

But I haven't been able to find a solution for this. Whether there is a?

Question 1: Is it possible to erase a simple script that allows [AnyTransportation<AnyVehicle>]

:?

Question 2: If simple script is allowed, is original script allowed?

The following is just a more detailed explanation of what I want to achieve with the original script

Original Screenplay - Extended

My initial need is to put anyone Transportation

, having any Vehicle

, which by itself has any Motor

within the same array:

let transportations: [AnyTransportation<AnyVehicle<AnyMotor>>] = [BusLine(), FerryLine()] // want to put `BusLine` and `FerryLine` in same array

      

+3


source to share


3 answers


If you want to express any transportation by any vehicle with any engine, then you need 3 boxes, each of which speaks in terms of "previous" erasable types. You don't want generic placeholders added in each of these fields, as you want to speak in terms of completely heterogeneous instances (for example, not transport with a specific type Vehicle

or any vehicle with a specific type Motor

).

Also, instead of using a class hierarchy to perform type erasure, you can use locks instead, which allows you to grab the underlying instance rather than store it directly. This removes much of the template from the source 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 : 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 = $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 are still building AnyMotor

, even though Motor

it has no associated types. This is because the protocols do not conform to themselves , so we cannot use Motor

to satisfy the Engine

associated type (which requires : Motor

) - we currently need to create a specific wrapper type for this.

+3


source


Hamish's solution is definitely the right way to do what you asked, but when you get into this erasing of styles there are a few questions to ask yourself.

Let's start at the end:

let transportations: [AnyTransportation<AnyVehicle<AnyMotor>>] = [BusLine(), FerryLine()] // want to put `BusLine` and `FerryLine` in same array

      

What can you do with transportations

? Seriously, what kind of code would you write to iterate over it without checking as?

? The only common method available is name

. You could not call anything else, because the types will not match at compile time.

This is really close to the example from my Beyond Crusty , and I think you should look for the same place for solutions. For example, instead of this:

struct RedBusLine: Transportation {
    let transport: Bus
    var name = "Red line"
    init(transport: Bus = Bus()) {
        self.transport = transport
    }
}

      



consider solutions that look like this (i.e. no protocols and all PAT problems evaporate):

let redBusLine = Transportation(name: "Red line",
                                transport: Vehicle(name: "Bus", 
                                                   motor: Motor(power: 100))

      

Then consider if you really think that Bus

is a structure. Are two tires with the same properties the same tire?

let red = Bus()
let blue = Bus()

      

Are the red and blue tires the same? If it is not, then it is not a value type. It is a reference type and should be a class. A lot of Swift's negotiations push us towards protocols and disgrace us by class, but Swift's actual design encourages exactly the opposite. Make sure you avoid activities because these are real value types, not just peer pressure. Don't use protocols just because it's Swift. I believe PAT is a tool for very specialized needs (like Collection) and not for solving most problems. (Before Swift 4, even a collection was a complete mess of protocol.)

+2


source


This article might help: https://gist.github.com/dtartaglia/0b5188eaa825b1239389b377d8cb23c1

Erasing two levels in Swift 3

I recently converted my project to Swift 3 ( https://github.com/dtartaglia/XStreamSwift ) and I had to deal with a problem that I couldn't find an answer to. This article focuses on the problem and solution that I found.

0


source







All Articles