skip to Main Content

I have this syntactical issue with my data structure. What makes it worse is that I can not talk about it in detail. The company I am employed at operates in the public transportation sector and I am under NDA and boss would kill me if I posted anything too specific. I hope you understand!

I have this perfect example though. There are no inconsistencies at all 😉
Well, okay, there are. However I am convinced that most of you out there are smart enough to get what is of importance here.

Basic structure:

class Propulsion {
    var horsePower: Double
    init(horsePower: Double) {
        self.horsePower = horsePower
    }
    static let pedes = Propulsion(horsePower: 0.2)
}

class Motor: Propulsion {
    var range: Double
    init(range: Double, horsePower: Double) {
        self.range = range
        super.init(horsePower: horsePower)
    }
    static let otto = Motor(range: 1000, horsePower: 100)
    static let electric = Motor(range: 400, horsePower: 200)
}

class Vehicle<P: Propulsion> {
    var propulsion: P
    init(propulsion: P) {
        self.propulsion = propulsion
    }
}

class Bicycle<P: Propulsion>: Vehicle<P> {
    var hasFrontSuspension: Bool
    init(hasFrontSuspension: Bool, propulsion: P) {
        self.hasFrontSuspension = hasFrontSuspension
        super.init(propulsion: propulsion)
    }
}

class Car<P: Propulsion>: Vehicle<P> {
    func rangePerHorsePower() -> Double where P: Motor {
        propulsion.range / propulsion.horsePower
    }
}

Now I would like to declare a parking spot for a car. Like so:

var carParkingSpot: ParkingSpot<Car<Motor>>

For the class ParkingSpot I have some class like this in mind:

class ParkingSpot<V: Vehicle<P>> where P: Propulsion {
    var vehicle: Vehicle<P>
    init(vehicle: Vehicle<P>) {
        self.vehicle = vehicle
    }
    func taxForRange() -> Double where P: Motor {
        vehicle.propulsion.range * 50
    }
}

From the last bit I get back a bunch of

Cannot find type ‘P’ in scope

This one doesn’t work either:

class ParkingSpot<V: Vehicle<P: Propulsion>>

Expected ‘>’ to complete generic argument list

This implementation works though:

class ParkingSpot<V: Vehicle<P>, P: Propulsion> {
    var vehicle: Vehicle<P>
    init(vehicle: Vehicle<P>) {
        self.vehicle = vehicle
    }
    func taxForRange() -> Double where P: Motor {
        vehicle.propulsion.range * 50
    }
}

However I don’t want to duplicate the Motor bit:

var carParkingSpot: ParkingSpot<Car<Motor>, Motor>

How can I accomplish this with just one generic parameter?

2

Answers


  1. This seems to work:

    class ParkingSpot<V: Vehicle<Propulsion>>
    {
        var vehicle: V
        init(vehicle: V)
        {
            self.vehicle = vehicle
        }
    
        func taxForEngineeNoise() -> Double
        {
            switch vehicle.propulsion
            {
            case is Motor:
                return vehicle.propulsion.horsePower * 50
    
            default:
                ...
            }
        }
    
        func taxForRange() -> Double
        {
            if let motor = vehicle.propulsion as? Motor
            {
                return motor.range * 50
            }
            else
            {
                ...
            }
        }
    }
    

    Alternatively, perhaps hide the duplication where you can?

    typealias ParkingSpotX = ParkingSpot<Car<Motor>, Motor>
    
    var parkingSpot: ParkingSpotX
    
    Login or Signup to reply.
  2. You may use the "Protocol oriented" approach:

    protocol PropulsionP {
        var horsePower: Double { get }
    }
    
    protocol MotorP: PropulsionP {
        var range: Double { get }
    }
    
    struct MotorS: MotorP {
        var range: Double
        var horsePower: Double
    
        init(range: Double, horsePower: Double) {
            self.range = range
            self.horsePower = horsePower
        }
    }
    
    protocol VehicleP {
        associatedtype P: PropulsionP
    
        var propulsion: P { get }
    }
    
    struct BicycleS<Prop: PropulsionP>: VehicleP {
        let hasFrontSuspension: Bool
        var propulsion: Prop
    
        init(
            hasFrontSuspension: Bool,
            propulsion: Prop
        ) {
            self.hasFrontSuspension = hasFrontSuspension
            self.propulsion = propulsion
        }
    }
    
    struct CarS<Prop: PropulsionP>: VehicleP {
        var propulsion: Prop
    
        func rangePerHorsePower() -> Double where P: MotorP {
            propulsion.range / propulsion.horsePower
        }
    }
    
    struct ParkingSpotS<V: VehicleP> {
        var vehicle: V
    
        init(vehicle: V) {
            self.vehicle = vehicle
        }
    
        func taxForRange() -> Double where V.P: MotorP {
            vehicle.propulsion.range * 50
        }
    }
    
    var carParkingSpot: ParkingSpotS<CarS<MotorS>>
    

    No double MotorS bit.
    Quod erat demonstrandum.

    I used the somewhat unusual naming to emphasize the point.

    (needed to make some edit, erronously typed Motor where I actually need MotorP)

    Update

    I was on the road with my preferred car and tried it out:

    var carParkingSpot: ParkingSpotS<CarS<MotorS>> = .init(
        vehicle: .init(
            propulsion: .init(
                range: 760,
                horsePower: 240
            )
        )
    )
    
    print(carParkingSpot.taxForRange())
    
    38000.0
    

    Alternatively you can use this initialiser:

    var carParkingSpot: ParkingSpotS = .init(
        vehicle: CarS(
            propulsion: MotorS(
                range: 760,
                horsePower: 240
            )
        )
    )
    

    Update

    Now suppose you are utilising a third party library which already provides a nice implementation of a motor.

    What you need to do is to implement an extension for their given class or struct TheirMotor which conforms to your protocol MotorP:

    import FancyMotors
    
    extension TheirMotor: MotorP {
        let range: Double {
            // calculate `range` in terms of the 
            // given API of `TheirMotor`:
            ...
            return range
        }
    }
    

    Then, you can use it like below:

    var carParkingSpot: ParkingSpotS = .init(
        vehicle: CarS(
            propulsion: TheirMotor( 
                distance: 760,
                power: 240
            )
        )
    )
    

    Note, that you use TheirMotor and need to use the appropriate initialiser to create it.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search