2012-06-30 61 views
2

這是表示類型的twoquestions的後續操作,表示類型是設計用於表示有界類型成員(或類似東西)的基礎類型的類型參數, 。我已經成功地創建了類的實例,例如ConcreteGarage,它們的實例有cars有界類型成員CarTypeScala:通過if語句更正表示類型的類型推斷

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 

我可以很容易地像Ferrari S和Mustang S創建的Car小號實例,並把它們放入一個ConcreteGarage,只要很簡單:

val newFerrari = new Ferrari(Benzin()) 
val newMustang = new Mustang(Benzin()) 

val ferrariGarage = new ConcreteGarage(Seq(newFerrari)) 
val mustangGarage = new ConcreteGarage(Seq(newMustang)) 

但是,如果我只返回一個或其他,根據國旗,並試圖把結果放入車庫,它會失敗:

val likesFord = true 
val new_car = if (likesFord) newFerrari else newMustang 

val switchedGarage = new ConcreteGarage(Seq(new_car)) // Fails here 

交換機單獨工作精細,它是失敗的,而神祕的錯誤ConcreteGarage構造函數的調用:

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 
        ^

我試圖把那些神奇的[C <: Car[C]]表示類型參數無處不在,但沒有找到魔法點成功。

回答

2

沒有任何有用的超類型FerrariMustang可以被別名。你需要用這種方法把世界包裹起來。

一種可能性是將Garage結構作爲方法添加到Car

另一種可能性是定義一些「世界」這需要生產兼容的汽車和車庫的護理:

trait World { 
    type CarType <: Car[CarType] 
    def newCar() : CarType 
    def newGarage(cars: Seq[CarType]) = new ConcreteGarage[CarType](cars) 
} 

class FerrariWorld extends World { 
    type CarType = Ferrari 
    def newCar() = new Ferrari(Benzin()) 
} 

class FordWorld extends World { 
    type CarType = Mustang 
    def newCar() = new Mustang(Benzin()) 
} 

def play(world: World) { 
    val car = world.newCar() 
    println(car) 
    val gar = world.newGarage(Seq(car)) 
    println(gar) 
} 

def test(likesFord: Boolean) { 
    val w = if(likesFord) new FordWorld else new FerrariWorld 
    play(w) 
} 

test(true) 
test(false) 

你可以看到,這可以得到相當幽閉恐懼症。所以這取決於你的目標情景。路徑依賴類型總是會導致未來的約束。考慮這個簡單的變型與類型參數,而不是:

trait Fuel { def liters: Int } 
trait Make { def color: String } 

case class Benzin(liters: Int = 0) extends Fuel 
case class Diesel(liters: Int = 0) extends Fuel 
case class Ferrari(color: String) extends Make 
case class Mustang(color: String) extends Make { def race() { println("Rrrroar")}} 

case class Car[M <: Make, F <: Fuel](make: M, fuel: F) { 
    def refuel(f: F): Car[M, F] = copy(make, f) 
} 

case class Garage[M <: Make](cars: Seq[Car[M,_]] = Seq.empty) { 
    def add(c: Car[M,_]) = copy(cars :+ c) 
    def remove(c: Car[M,_]) = copy(cars.filterNot(_ == c)) 
    def refuel[F <: Fuel](c: Car[M,F], f: F) = copy(cars.map { 
     case `c` => c.refuel(f) 
     case other => other 
    }) 
}  

val g0 = Garage[Mustang]() 
val m = Car(Mustang("black"), Benzin()) 
val f = Car(Ferrari("red"), Benzin()) 
val g1 = g0.add(f)    // forbidden 
val g1 = g0.add(m)    // ok 
val g2 = g1.refuel(f, Benzin(45)) // forbidden 
val g2 = g1.refuel(m, Diesel(45)) // forbidden 
val g2 = g1.refuel(m, Benzin(45)) // ok 
g2.cars.foreach(_.make.race()) // ok 

結論:不要扯到...

enter image description here

+0

我知道我應該聽從你的早期警告關於表示類型。你的答案的後半部分似乎是解決這個問題的方法。要選擇'Make'-'Fuel'組合的一個子集,可以設置類型別名,比如'MustangCar = Car [Mustang,Benzin]'或者CarWithBenzin = Car [Make,Benzin]'。對於我真正的項目(不幸的是不涉及賽車野馬),這應該是有用的。 – drhagen

+0

請注意,表示類型並不是問題 - 整個Scala集合庫成功使用它們而沒有「感染」其他任何東西;真正的問題在於使用路徑依賴類型構建可擴展的東西。當您將多種類型組合在一起時,「世界」方法可能很有用,但我不確定是否屬於這種情況。 –