Проблема параметризованного типа в Scala с возвратом экземпляра того же типа - PullRequest
5 голосов
/ 24 февраля 2011

Далее я представлю только очень уменьшенные версии моего кода Scala. Достаточно, чтобы показать проблему. Ненужные блоки кода будут уменьшены до ....

Часть, которая работает

Я создал векторную библиотеку (то есть для моделирования математических векторов, а не векторов в смысле scala.collection.Vector). Основная черта выглядит так:

trait Vec[C] extends Product {
  def -(o:Vec[C]):Vec[C] = ...
  ...
}

Я создал множество подтипов для определенных векторов, например Vec2 для двумерных векторов или Vec2Int, специализированный для двумерных Int векторов.

Подтипы сужают типы возвращаемых данных некоторых операций. Например, вычитание Vec2Int из другого вектора не вернет общий Vec[Int], а более конкретный Vec2Int.

Кроме того, я объявил эти методы в очень специфических подтипах, таких как Vec2Int, как final, что позволило компилятору выбирать эти методы для встраивания.

Это работает очень хорошо, и я создал быструю и удобную библиотеку для векторных вычислений.

Основываясь на этом, я теперь хочу создать набор типов для моделирования основных геометрических фигур. Основная черта формы выглядит так:

trait Shape[C, V <: Vec[C]] extends (V=>Boolean) {
  def boundingBox:Box[C,V]
}

Где Box будет подтипом Shape, моделирующим n-мерный прямоугольник.

Часть, которая не работает

Теперь я попытался определить поле:

trait Box[C, V <: Vec[C]] extends Shape[C,V] {
  def lowCorner:V
  def highCorner:V
  def boundingBox = this
  def diagonal:V = highCorner - lowCorner // does not compile
}

Метод diagonal не компилируется, потому что метод Vec.- возвращает Vec[C], а не V.

Конечно, я мог бы заставить diagonal вернуть Vec[C], но это было бы неприемлемо во многих отношениях. На этот раз я бы потерял оптимизацию компилятора для определенных Vec подтипов. Кроме того, когда у вас, например, есть прямоугольник, описываемый двумя двумерными Float векторами (Vec2Float), имеет смысл предположить, что диагональ также равна Vec2Float. Я не хочу потерять эту информацию.

Моя попытка решить проблему

Следуя примеру иерархии коллекций Scala, я ввел тип VecLike:

trait VecLike[C, +This <: VecLike[C,This] with Vec[C]] {
  def -(o:Vec[C]):This
  ...
}

и я заставил Vec расширить его:

trait Vec[C] extends Product with VecLike[C, Vec[C]] ...

(Затем я хотел бы создать более конкретные подтипы VecLike, например Vec2Like или Vec3Like, чтобы сопровождать мою иерархию Vec типов.)

Теперь новое определение для Shape и Box выглядит следующим образом:

trait Shape[C, V <: VecLike[C,V] with Vec[C]] ...

trait Box[C, V <: VecLike[C,V] with Vec[C]] extends Shape[C,V] {
  ...
  def diagonal:V = highCorner - lowCorner 
}

Тем не менее, компилятор жалуется:

Error: type mismatch;
found: Vec[C]
required: V

Это смущает меня. Тип VecLike явно возвращает This в методе минус, что соответствует параметру типа V типа Box. Я вижу, что метод минус Vec по-прежнему возвращает Vec[C], но почему компилятор не может использовать тип возврата метода минус VecLike?

Как я могу исправить эту проблему?

1 Ответ

6 голосов
/ 24 февраля 2011

Мой совет - гораздо меньше работать над пропуском кода, который, по вашему мнению, не имеет значения, и просто показать код.Удивительно, как часто людям удается удалить часть, которая имеет значение.Мантра гласит: «Если вы не знаете, почему это не работает, значит, вы не знаете, что имеет значение».Это очень серьезный, искренний совет: я могу помочь вам за пять секунд, если вы дадите мне код, который будет компилироваться, за исключением того, чего вы не понимаете, или я могу помочь вам через пять минут, если мне придется восстановить все части, которые выопущены.Угадай, какой из них случается чаще.

В коде.После того, как я угадаю, как биты с первой попытки заполняются во второй попытке, он компилируется точно так, как указано.(Эта фаза «угадывания» - еще одна веская причина показать код заранее.)

trait VecLike[C, +This <: VecLike[C, This] with Vec[C]] {
  def -(o: Vec[C]): This
}

trait Vec[C] extends Product with VecLike[C, Vec[C]] { }

trait Shape[C, V <: VecLike[C,V] with Vec[C]] { }

trait Box[C, V <: VecLike[C,V] with Vec[C]] extends Shape[C, V] {
  def lowCorner: V
  def highCorner: V
  def boundingBox = this
  def diagonal: V = highCorner - lowCorner 
}

% scalac281 a.scala 
%
...