Разрешающие типы в F-ограниченном полиморфизме
У меня есть эти модели:
trait Vehicle[T <: Vehicle[T]] { def update(): T }
class Car extends Vehicle[Car] { def update() = new Car() }
class Bus extends Vehicle[Bus] { def update() = new Bus() }
Если я получу экземпляр Vehicle[Car]
и вызвать update()
Я получу Car
, поскольку Car
продолжается Vehicle[Car]
(или, проще говоря, Car is a Vehicle [Car]), я могу смело установить тип результата в явном виде Vehicle[Car]
:
val car = new Car
val anotherCar = car.update()
val anotherCarAsVehicle: Vehicle[Car] = car.update() // works as expected
Но если я хочу, скажем, ставить случаи Car
а также Bus
вместе в один список, то я должен установить тип списка Vehicle[_ <: Vehicle[_]]
(имея список просто Vehicle[_]
и ссылаясь update()
на элемент даст Any
, но я хочу иметь возможность использовать update()
поэтому я должен использовать F-ограниченный тип). Использование экзистенциальных типов приводит к нарушению отношений типов, потому что, как только я получаю базовый автомобиль / автобус из Транспортного средства, я больше не могу приводить его к Транспортному средству, потому что... ну, это просто некоторый экзистенциальный тип:
val seq = List[Vehicle[_ <: Vehicle[_]]](new Car, new Bus)
val car = seq.head.update()
val carAsVehicle: Vehicle[_ <: Vehicle[_]] = seq.head.update() // fails to compile
Так, Vehicle
параметризован с некоторым типом T
который является подтипом Vehicle[T]
, Когда я вырываю T
(используя update()
), в случае конкретных типов это нормально - например, если я вырву Car
Я могу смело утверждать, что я вырвал Vehicle[Car]
так как Car <: Vehicle[Car]
, Но если я вырву экзистенциальный тип, я ничего не могу с этим поделать. Более ранний пример работал, потому что Car
это Vehicle[Car]
, но в этом случае _
это не Vehicle[_]
,
Уточняю мой конкретный вопрос: есть ли способ добиться этого для моделей, указанных выше (Автомобиль, Автомобиль, Автобус)?
def sameType[T, U](a: T, b: U)(implicit evidence: T =:= U) = true
val seq = List[Vehicle[_ <: Vehicle[_]]](new Car, new Bus)
sameType(seq.head.update +: seq.tail, seq) // true
Обратите внимание, что вы можете изменить данные черты, классы и тип seq
, но есть одно ограничение: update()
должен вернуться T
не Vehicle[T]
,
Я знаю, что с помощью бесформенного HList
решит проблему, так как мне не придется использовать экзистенциальные типы (у меня просто будет список автомобилей и автобусов, и эта информация о типах будет сохранена). Но мне интересно для этого конкретного случая использования с простым List
,
РЕДАКТИРОВАТЬ:
@ РомКазанова да, это бы сработало конечно, но мне нужно сохранить тот же тип до и после update()
(вот упрек для усилий, хотя;)).
Я считаю, что это невозможно без HList или подобной структуры данных, потому что объединение автомобилей и автобусов вынуждает нас использовать тип транспортного средства, который теряет информацию о том, был ли его базовый тип автомобилем, автобусом или чем-то еще (все, что мы можем знать, это то, что это был какой-то тип _ <: Vehicle
). Но я хочу уточнить у вас, ребята.
2 ответа
Я не очень хорош с экзистенциальными типами, поэтому я не могу объяснить слишком много об этом:-p Но когда вы меняете тип seq
в List[Vehicle[T] forSome {type T <: Vehicle[T]}]
кажется, что все "работает". Имейте в виду, вы должны передать тип List
конструктор / применить метод.
scala> val seq = List[Vehicle[T] forSome {type T <: Vehicle[T]}](new Car, new Bus)
seq: List[Vehicle[T] forSome { type T <: Vehicle[T] }] = List(Car@31e53802, Bus@54d569e7)
scala> sameType(seq.head.update +: seq.tail, seq)
res3: Boolean = true
scala> seq.head.update
res4: T forSome { type T <: Vehicle[T] } = Car@79875bd2
scala> seq.head.update.update
res5: T forSome { type T <: Vehicle[T] } = Car@6928c6a0
scala> new Car +: seq
res6: List[Vehicle[T] forSome { type T <: Vehicle[T] }] = List(Car@51f0a09b, Car@31e53802, Bus@54d569e7)
Я думаю, что главное, что нужно сделать из этого ответа, это то, что это позволяет вам разобрать рекурсивный характер Vehicle
Конструктор типов.
Я не уверен, что рекомендовал бы это хотя...
Есть два способа решить это:
val carAsVehicle: Vehicle[_] = seq.head.update()
или используйте сопоставление с образцом
val carAsVehicle: Vehicle[Car] = seq.head match {
case car: Vehicle[Car] => car.update()
}
Но интересно, что:
val seq = List[Vehicle[_ <: Vehicle[_]]](new Car, new Bus)
val vehicleAsVihecles: List[Vehicle[_]]= seq.map(_.update()) // compiled
val vehicleAsVihecles1: List[Vehicle[_ <: Vehicle[_]]]= seq.map(_.update()) //not compiled
def somedef(vehicles: List[Vehicle[_ <: Vehicle[_]]]) = vehicles.map(_.update()) //compiled
somedef(seq)