简体   繁体   中英

Operation on an array of structs implementing Equatable

I have an array of different structs, all implementing Equatable protocol and am trying to pass it to a function that expects a collection where T.Iterator.Element: Equatable . I know how to solve this problem by using classes and just creating a class Vehicle: Identifiable, Equatable , and then make Car and Tractor implement Vehicle . However I'd like to know if this is possible with using structs and protocols?

Here's a contrived example of what I'm trying to do

//: Playground - noun: a place where people can play

protocol Identifiable {
    var ID: String { get set }
    init(ID: String)
    init()
}

extension Identifiable {
    init(ID: String) {
        self.init()
        self.ID = ID
    }
}

typealias Vehicle = Identifiable & Equatable

struct Car: Vehicle {
    var ID: String

    init() {
        ID = ""
    }

    public static func ==(lhs: Car, rhs: Car) -> Bool {
        return lhs.ID == rhs.ID
    }
}

struct Tractor: Vehicle {
    var ID: String

    init() {
        ID = ""
    }

    public static func ==(lhs: Tractor, rhs: Tractor) -> Bool {
        return lhs.ID == rhs.ID
    }
}

class Operator {
    func operationOnCollectionOfEquatables<T: Collection>(array: T) where T.Iterator.Element: Equatable {
    }
}

var array = [Vehicle]() //Protocol 'Equatable' can only be used as a generic constraint because Self or associated type requirements

array.append(Car(ID:"VW"))
array.append(Car(ID:"Porsche"))
array.append(Tractor(ID:"John Deere"))
array.append(Tractor(ID:"Steyr"))

var op = Operator()
op.operationOnCollectionOfEquatables(array: array) //Generic parameter 'T' could not be inferred

The problem is, as the error says, you cannot use protocols with Self or associated type requirements as actual types – as you'd lose the type information for what those requirements were. In this case, you'd lose the type information for the parameters of the == implementation – as Equatable says they must be the same type as the conforming type (ie Self ).

The solution is almost always to build a type eraser . In the case of expecting types to be equal if their id properties are equal, this can be as simple as just storing the id property and comparing it in the == implementation.

struct AnyVehicle : Equatable {

    static func ==(lhs: AnyVehicle, rhs: AnyVehicle) -> Bool {
        return lhs.id == rhs.id
    }

    let id : String

    init<T : Vehicle>(_ base: T) {
        id = base.id
    }
}

(Note that I renamed your ID property to id in order to conform with Swift naming convention)

However, a more general solution would be to store a function in the type eraser that can compare two arbitrary Vehicle conforming instances based on their == implementation, after type-casting to ensure they are the same type as the concrete type that the type eraser was created with.

struct AnyVehicle : Equatable {

    static func ==(lhs: AnyVehicle, rhs: AnyVehicle) -> Bool {

        // forward to both lhs's and rhs's _isEqual in order to determine equality.
        // the reason that both must be called is to preserve symmetry for when a
        // superclass is being compared with a subclass.
        // if you know you're always working with value types, you can omit one of them.
        return lhs._isEqual(rhs) || rhs._isEqual(lhs)
    }

    let base: Identifiable

    private let _isEqual: (_ to: AnyVehicle) -> Bool

    init<T : Vehicle>(_ base: T) {

        self.base = base

        _isEqual = {

            // attempt to cast the passed instance to the concrete type that
            // AnyVehicle was initialised with, returning the result of that
            // type's == implementation, or false otherwise.
            if let other = $0.base as? T {
                return base == other
            } else {
                return false
            }
        }
    }
}

print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Tractor(id: "foo"))) // false
print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Car(id: "bar"))) // false
print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Car(id: "foo"))) // true

var array = [AnyVehicle]()

array.append(AnyVehicle(Car(id: "VW")))
array.append(AnyVehicle(Car(id: "Porsche")))
array.append(AnyVehicle(Tractor(id: "John Deere")))
array.append(AnyVehicle(Tractor(id: "Steyr")))

var op = Operator()

// compiles fine as AnyVehicle conforms to Equatable.
op.operationOnCollectionOfEquatables(array: array) 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM