Разрешающие типы в 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)
Другие вопросы по тегам