2

When using the variable all in the Enum MotorVehicle in order to pass all enum cases, the code does not compile. But doing the same thing witnout using said variable works. Why is this? I want to make the Enum "aware" of all its cases, not having to create arrays of it everywhere, or having to use "map". And using the solution with the variable allAsProtocol (used by version 7) is just plain ugly IMHO. Any ideas?

Swift 2.2 code (just paste the code below in a Playground)

    protocol Motorized {
        func startEngine()
    }

    enum MotorVehicle: String, Motorized {
        case Car
        case MotorCycle

        static var all: [MotorVehicle] {
            return [.Car, .MotorCycle]
        }

        static var allAsProtocol: [Motorized] {
            return [MotorVehicle.Car, MotorVehicle.MotorCycle]
        }

        func startEngine() {
            print("Starting \(self.rawValue)")
        }
    }

    func start(motorized motorized: [Motorized]) {
        for motor in motorized {
            start(motorized: motor)
        }
    }

    func start(motorized motorized: Motorized) {
        motorized.startEngine()
    }

    // VERSION 1
    start(motorized: MotorVehicle.Car) //works

    // VERSION 2
    start(motorized: [MotorVehicle.Car, MotorVehicle.MotorCycle]) //works

    // VERSION 3
    /* In my opinion, this is logically identical to the code at the previous line...  */
    start(motorized: MotorVehicle.all) //Compilation Error - Cannot convert value of type ‘[MotorVehicle]’ to expected argument type ‘[Motorized]'

    // VERSION 4
    start(motorized: (MotorVehicle.all as! [Motorized])) //Compilation Error - 'Motorized' is not a subtype of 'MotorVehicle'

    // VERSION 5
    start(motorized: (MotorVehicle.all as [Motorized])) //Compilation Error - cannot convert value of type [MotorVehicle] to type [Motorized] in coercion

    // VERSION 6
    start(motorized: (MotorVehicle.all.map { return $0 as Motorized } )) //works

    // VERSION 7
    start(motorized: MotorVehicle.allAsProtocol) //works
Sajjon
  • 8,938
  • 5
  • 60
  • 94
  • See both [this Q&A](http://stackoverflow.com/questions/37188580/why-isnt-somestruct-convertible-to-any) and [this Q&A](http://stackoverflow.com/questions/38387256/type-conversion-when-using-protocol-in-swift). It all starts with the fact that generics are invariant in Swift, therefore `[MotorVehicle]` is seen as a completely unrelated type to `[Motorized]`. Although `Array` can perform some implicit 'under the hood' conversions between array types, it requires an explicit conversion (via `map`) to an array of abstract type, as this conversion is potentially expensive. – Hamish Aug 08 '16 at 13:07
  • Thanks @Hamish, makes sense... But can you come up with any better solution than the one used on Version 6 or 7? Is there any more elegant solution? :) – Sajjon Aug 08 '16 at 13:19
  • Because `map` can infer the `as Motorized` from the context, and because you don't need an explicit `return` in a single expression closure, you could simplify the call down to `start(motorized: MotorVehicle.all.map{$0})`. – Hamish Aug 08 '16 at 13:28
  • @Hamish wow okay, so the compiler can infer the protocol conformance by writing MotorVehicle.all.map{$0}), that's a bit odd, but much nicer syntax, thanks! – Sajjon Aug 08 '16 at 13:37
  • No problem :) It works because the compiler can see that `start(_:)` expects a `[Motorized]` argument, therefore it can infer that the generic parameter `T` in the `map(_:)` call is `Motorized`, therefore it can implicitly upcast each element from `MotorVehicle` to `Motorized` (and because of the conformance, this satisfies the type inference engine). – Hamish Aug 08 '16 at 13:41

0 Answers0