4

Can I make an enum generic (different type for each case), so I can use its cases to infer a type for a generic class?

I have an example here:

class Config {
    let name: String
    init(_ name: String) {
        self.name = name
    }
}

class CarConfig: Config {
    static let sports = CarConfig("sports")
    static let van = CarConfig("van")
}

class BikeConfig: Config {
    static let road = BikeConfig("road")
    static let mtb = BikeConfig("mtb")
}

enum VehicleType {
    case car, bike, scooter
}

class Vehicle<C: Config> {

    let type: VehicleType
    let config: C

    init(type: VehicleType, config: C) {
        self.type = type
        self.config = config
    }

}

let bike = Vehicle(type: .bike, config: BikeConfig.mtb)

bike.config.name // mtb

What I would like to do is initiate a Vehicle like this:

let bike = Vehicle(type: .bike, config: .mtb)

I want the compiler to infer the BikeConfig, so I can leave it out. I want the compiler to know that a Vehicle with type == VehicleType.bike always has a Config that is a BikeConfig.

I would have to change my Vehicle obviously:

class Vehicle<C: Config> {

    let type: VehicleType<C>
    let config: C

    init(type: VehicleType<C>, config: C) {
        self.type = type
        self.config = config
    }

}

And now make enum VehicleType enum VehicleType<C: Config>.

No Idea where to go from here though. Any help? :)

[UPDATE: added scooter case to VehicleType]

Musection
  • 135
  • 6

2 Answers2

4

You can use Associated Values with the enum cases as whatever type you want.

Create enum VehicleType with case car with CarConfig and case bike with BikeConfig as the associated types,

    enum VehicleType {
        case car(CarConfig)
        case bike(BikeConfig)
    }

Now, the definition for class Vehicle can be modified to

    class Vehicle {
        let type: VehicleType
        init(type: VehicleType) {
            self.type = type
        }
    }

Create the Vehicle instance using,

    let bike = Vehicle(type: .bike(.mtb))

B25Dec
  • 2,301
  • 5
  • 31
  • 54
PGDev
  • 23,751
  • 6
  • 34
  • 88
2

You've got it backwards, you shouldn't infer the generic type depending on the enum value, because that means you want to determine some compile-time thing (generic type) with a value that is possibly known at runtime (the value of the enum).

Therefore, we need to make the type parameter a compile-time-only thing, i.e. also a type parameter.

You first introduce a VehicleTypeProtocol, and a struct implementing that protocol for each of the enum case:

protocol VehicleTypeProtocol {
    // this associated type creates the link between a vehicle type and a config type
    associatedtype ConfigType: Config
    // this is so that we can assign to Vehicle.type
    static var type: VehicleType { get }
}

struct Car : VehicleTypeProtocol {
    typealias ConfigType = CarConfig
    static var type: VehicleType { .car }
}

struct Bike : VehicleTypeProtocol {
    typealias ConfigType = BikeConfig
    static var type: VehicleType { .bike }
}

struct Scooter: VehicleTypeProtocol {
    typealias ConfigType = BikeConfig
    static var type: VehicleType { .scooter }
}

And then the initialiser can be implemented like this:

init<T: VehicleTypeProtocol>(type: T.Type, config: C) where T.ConfigType == C {
    self.type = T.type
    self.config = config
}

Usage:

let bike = Vehicle(type: Bike.self, config: .mtb)

But man, this is convoluted...

Sweeper
  • 213,210
  • 22
  • 193
  • 313