This is a follow-up to two questions on representation types, which are type parameters of a trait designed to represent the type underlying a bounded type member (or something like that). I’ve had success creating instances of classes, e.g ConcreteGarage, that have instances cars of bounded type members CarType.
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
})
}
class ConcreteGarage[C <: Car[C]](val cars: Seq[C]) extends Garage {
type CarType = C
def copy(cars: Seq[C]) = new ConcreteGarage(cars)
}
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 Ferrari(val fuel: Benzin) extends Car[Ferrari] {
type FuelType = Benzin
def copy(fuel: Benzin) = new Ferrari(fuel)
}
class Mustang(val fuel: Benzin) extends Car[Mustang] {
type FuelType = Benzin
def copy(fuel: Benzin) = new Mustang(fuel)
}
trait Fuel
case class Benzin() extends Fuel
I can easily create instances of Cars like Ferraris and Mustangs and put them into a ConcreteGarage, as long as it’s simple:
val newFerrari = new Ferrari(Benzin())
val newMustang = new Mustang(Benzin())
val ferrariGarage = new ConcreteGarage(Seq(newFerrari))
val mustangGarage = new ConcreteGarage(Seq(newMustang))
However, if I merely return one or the other, based on a flag, and try to put the result into a garage, it fails:
val likesFord = true
val new_car = if (likesFord) newFerrari else newMustang
val switchedGarage = new ConcreteGarage(Seq(new_car)) // Fails here
The switch alone works fine, it is the call to ConcreteGarage constructor that fails with the rather mystical error:
error: inferred type arguments [this.Car[_ >: this.Ferrari with this.Mustang <: this.Car[_ >: this.Ferrari with this.Mustang <: ScalaObject]{def fuel: this.Benzin; type FuelType<: this.Benzin}]{def fuel: this.Benzin; type FuelType<: this.Benzin}] do not conform to class ConcreteGarage's type parameter bounds [C <: this.Car[C]]
val switchedGarage = new ConcreteGarage(Seq(new_car)) // Fails here
^
I have tried putting those magic [C <: Car[C]] representation type parameters everywhere, but without success in finding the magic spot.
There aren’t any useful super types to which
FerrariandMustangcan be aliased. You need to wrap the world inside out with this approach.One possibility is to add the
Garageconstruction as a method toCar.Another possibility is to define some ‘world’ which takes care of producing compatible cars and garages:
You can see that this can get quite claustrophobic. So it really depends on your target scenario. Path-dependent types always cause future constraints. Consider this rather simple variant with type parameters instead:
Conclusion: Don’t get side-tracked…