drhagen
drhagen

Reputation: 9610

Scala: Constraining the type of a parameter based on the type member of another parameter

I have two traits, each with a type parameter for one of its members. In the first trait, I have a function that takes an instance of the second trait and an instance of the second trait's type member. This function calls a function in the second trait that is expecting that instance of its type member. However, I cannot figure out how to properly parameterize the call so that this actually works. Here is a simplified example that fails:

trait Garage {
  type CarType <: Car
  def Cars: Seq[CarType]

  def copy(Cars: Seq[CarType]): Garage

  def Refuel(car: CarType, fuel: CarType#FuelType): Garage = {
    val car_index = Cars.zipWithIndex.find(_._1 == car).get._2

    copy(Cars.updated(car_index, car.Refuel(fuel)))
  }
}

trait Car {
  type FuelType <: Fuel
  def Fuel: FuelType

  def copy(Fuel: FuelType): Car

  def Refuel(fuel: FuelType): Car = {
    copy(fuel)
  }
}

trait Fuel

This fails with the following error:

error: type mismatch;
 found   : fuel.type (with underlying type Garage.this.CarType#FuelType)
 required: car.FuelType
    copy(Cars.updated(car_index, car.Refuel(fuel)))
                                            ^

How do I constrain the Garage.Refuel function so that it accepts a Car and any Fuel that is acceptable to that type of Car?

Upvotes: 3

Views: 381

Answers (2)

0__
0__

Reputation: 67330

Although Daniel's answer works, I would like to point out an alternative, which is kind of my own panacea. I had been struggling a lot with getting path dependent types right, and ended up with the following strategy. It's a bit more 'ugly' as you now need to write an additional type parameter, but this approach has never let me down:

trait Garage {
  type CarType <: Car[CarType]  // CarType appears as representation type on the right
  def cars: Seq[CarType]

  def copy(Cars: Seq[CarType]): Garage

  def refuel(car: CarType, fuel: CarType#FuelType): Garage = copy(
    cars.map {  // map is more concise for what you try to achieve
      case `car` => car.refuel(fuel)  // backticks to find the particular car
      case other => other
    })
}

trait Car[C <: Car[C]] {  // add a 'representation type'
  type FuelType <: Fuel
  def fuel: FuelType

  // use 'C' instead of 'Car' everywhere, and qualify the type member with 'C#'
  def copy(fuel: C#FuelType): C

  def refuel(fuel: C#FuelType): C = copy(fuel)
}

trait Fuel

I don't know if this 'representation type' concept has a formal name (I would be interested to know). I tried to look up who taught me this, but didn't find it (at least in stackoverflow).

Upvotes: 4

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297295

Try:

def Refuel(car: CarType)(fuel: car.FuelType): Garage = {

Upvotes: 7

Related Questions