Имеют некоторый тип T
и абстрактный класс X[T]
, и каков главный смысл, для каждого конкретного типа T, если он определен, существует только один подкласс X[T]
, например, IntX extends X[Int]
, которыйявляется единственным подклассом для X[T]
с T = Int
.То есть теоретически у нас есть инъективное отображение T -> X[T]
для некоторого набора типов.
Давайте посмотрим на два простых определения:
trait Context[T] {
type XType <: X[T]
}
abstract class X[T] extends Context[T] {
def plus1(that: XType): XType = ??? /* doesn't matter */
def plus2(that: XType): XType = that plus1 this
def sum(x1: XType, x2: XType): XType = x1.plus1(x2)
}
Здесь мы видим наше X[T]
снекоторые методы.Чтобы иметь правильные конечные типы в конкретных унаследованных подклассах, я использую XType
как тип подкласса, унаследованного от X[T]
.Например, вот так:
trait IntContext extends Context[Int] {
type XType = IntX
}
class IntX extends X[Int] with IntContext
А затем метод IntX.plus1
принимает IntX
и возвращает IntX
, а не X[Int]
, так что это краткое объяснение довольно абстрактного примера.Context
используется для хранения всей информации о типах и конструкторах ткани, связанных с каждым используемым типом T. Что ж, есть более содержательный пример Context
, просто для правильного понимания вещей:
trait Context[V <: ArithmType[V]] { /* V such as Int, Double */
type Point <: AbstractPoint[V]
type Line <: AbstractLine[V]
type Rect <: AbstractRect[V]
...
def newPoint(x: V, y: V): Point
def newLine(v1: Point, v2: Point): Line
def newRect(p: Point, w: V, h: V): Rect
...
def pointCompanion: AbstractPoint.Companion[V]
def lineCompanion: AbstractLine.Companion[V]
def rectCompanion: AbstractRect.Companion[V]
...
}
Проблема:
Код с X[T]
не скомпилируется.Конечно, если мы рассмотрим два последних метода, мы получим следующие ошибки:
Type mismatch, expected: that.XType, actual: X[T]
Type mismatch, expected: x1.XType, actual: X.this.XType
Мы увидим, что компилятор обрабатывает собственный тип каждого экземпляра переменной XType как отличающийся друг от друга.И это правильно, конечно, но компилятор не знает, что такое инъекция нашего наследования: для фиксированного типа T
все XType
значения типов одинаковы.
Как я могу реализовать такоелогика для преодоления этого?
Я разработал одно решение, но оно довольно грязное.Перепишите код:
trait Context[T] {
type XType <: X[T]
implicit def cast(x: X[T]): XType = x.asInstanceOf(XType)
}
abstract class X[T] extends Context[T] {
def plus1(that: XType): XType = ??? /* doesn't matter */
def plus2(that: XType): XType = that plus1 that.cast(this)
def sum(x1: XType, x2: XType): XType = x1 plus1 x1.cast(x2)
}
Без неявного приведения методы будут выглядеть следующим образом:
def plus2(that: XType): XType = cast(that plus1 that.cast(this))
def sum(x1: XType, x2: XType): XType = cast(x1 plus1 x1.cast(x2))
asInstanceOf
- трансляция не завершится сбоем, поскольку мы знаем о нашем ограничении на приемистость.Можно использовать сопоставление с образцом, но это детали.
Основным недостатком этого решения является необходимость рефакторинга кода класса: мы помещаем некоторые загромождающие броски в части бизнес-логики.
Делает ли этоРешение имеет право быть использованным?Какие у вас есть идеи?
Редактировать: Есть ли способ использовать технику Aux
в этом случае?