0

This is a follow-up to a previous question where I had a trait Garage with a type member CarType, which itself had a type member FuelType, and I needed a function refuel that could take an instance of CarType as the first argument and an instance of the first argument's FuelType as the second argument.

The answer, the two traits below, was to give Car a representation type C <: Car[C]. The problem that I now have is that I can't figure out how to define the type parameter on the concrete classes implementing Garage, e.g. ConcreteGarage below.

trait Fuel

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

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

  def refuel(car: CarType, fuel: CarType#FuelType): Garage = copy(
    cars.map {
      case `car` => car.refuel(fuel)
      case other => other
    })
}

trait Car[C <: Car[C]] {
  type FuelType <: Fuel
  def fuel: FuelType

  def copy(fuel: C#FuelType): C

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

class ConcreteGarage(val cars: Seq[ConcreteGarage#CarType]) extends Garage {
  type CarType = Car[CarType] // Nope
  //type CarType = Car[Any] // Nope
  //type CarType = Car[Nothing] // Nope
  //type CarType = Car[Car] // Nope
  //type CarType <: Car[CarType] // Nope
  def copy(cars: Seq[CarType]) = new ConcreteGarage(cars)
}
Community
  • 1
  • 1
drhagen
  • 8,331
  • 8
  • 53
  • 82

1 Answers1

1

That's why I said beware of the ugliness of having to carry around another type parameter for the rest of your life :)

class ConcreteGarage[C <: Car[C]](val cars: Seq[C]) extends Garage {
  type CarType = C
  def copy(cars: Seq[C]) = new ConcreteGarage(cars)
}

Of course, its simpler if you have a particular garage.

case class Benzin(litres: Int) extends Fuel
case class Mustang(fuel: Benzin) extends Car[Mustang] {
   type FuelType = Benzin
   def copy(fuel: Benzin) = Mustang(fuel)
}
case class MustangGarage(cars: Seq[Mustang]) extends Garage {
   type CarType = Mustang
   def copy(cars: Seq[Mustang]) = MustangGarage(cars)
}

val m = Mustang(Benzin(0))
val g0 = MustangGarage(Seq(m))
val g1 = g0.refuel(m, Benzin(45))
0__
  • 66,707
  • 21
  • 171
  • 266
  • What do you mean? The 'boilerplate' in the general case is the qualification `[C <: Car[C]]` -- you need to chose: Either you want 'easier' types, then relax the constraints and allow any car to be used with any type of fuel; you can even drop type parameters altogether; or you want strong type constraints, then you'll have a minimum of effort to state those constraints. – 0__ Jun 30 '12 at 21:23
  • The general qualifications `[C <: Car[C]]` for representation types eventually need to qualify every trait that uses them, which then need to qualify every trait that uses *them*. Your rewrite of this problem using existential types in the [follow-up](http://stackoverflow.com/questions/11277656/) to this question seems to provide a better long-term solution to these kinds of structures. – drhagen Jul 01 '12 at 11:45
  • Exactly; you need to carry around at least one such type, whether that's `Car` or `World`. If the number of type parameters grows, it can be the less bitter medicine to stick with the world approach. – 0__ Jul 01 '12 at 12:01