Я играю с зависимыми (от пути) типами в Scala и наткнулся на следующий сценарий, для которого я не могу найти хорошего решения. Предположим, я хочу иметь некоторую иерархию зависимых типов, и я хочу, чтобы каждый из них имел обратную ссылку на свой объект «владелец». Я хочу, чтобы эта обратная ссылка могла вызывать некоторые методы для правильных объектов-«владельцев». Как правильно это делать?
Вот небольшой пример. Есть «базовая» черта Outer
с зависимым типом Inner
. Базовая черта Outer
определяет некоторый метод double
, который работает с зависимым типом. Существует также особый класс ConcreteOuter
с определенным зависимым классом ConcreteInner
, который использует простой Int
для значения.
trait Outer {
outerSelf =>
trait BaseInner {
val outer: outerSelf.type = outerSelf
def asDependent: outer.Inner // #1
// def asDependent: outerSelf.Inner // #2
}
type Inner <: BaseInner
def double(inner: Inner): Inner
}
class ConcreteOuter extends Outer {
case class ConcreteInner(val v: Int) extends BaseInner {
override def asDependent = this
}
type Inner = ConcreteInner
def createInner(v: Int): Inner = new ConcreteInner(v)
override def double(inner: Inner): Inner = new ConcreteInner(2 * inner.v)
}
Все идет нормально. Теперь предположим, что я хотел бы иметь возможность вызывать этот double
метод в контексте, где у меня есть только экземпляр некоторого Inner
класса, но не соответствующий Outer
-экземпляр. Например, давайте попробуем создать другой double
метод, который просто вызывает исходный Outer.double
в каком-то другом (независимом) контексте:
object DepTest extends App {
//def double(inner: Outer#Inner) = inner.outer.double(inner) // #3
def double(inner: Outer#Inner) = inner.outer.double(inner.asDependent) // #4
val c1 = new ConcreteOuter
val i1 = c1.createInner(123)
val d1 = double(i1)
println(d1)
}
Этот код компилируется, но требует довольно уродливого взлома asDependent
. Если я использую строку №3 вместо строки №4, код не компилируется. Если я разделю строку №3 следующим образом, код больше не будет компилироваться
def double(inner: Outer#Inner) = {
val outer = inner.outer
outer.double(inner.asDependent)
}
Более того, если я заменю строку №1 строкой №2, даже взлом asDependent
перестанет работать.
Таким образом, иногда кажется, что компилятор каким-то образом знает, что поле outer
объекта Inner
и объект «владелец», также известный как outerSelf
, - это одно и то же, а иногда это не так, и непонятно, как убедить компилятор, если он не признать их одним и тем же.
Есть ли способ обойти это? Или это совершенно неправильный подход к моей проблеме? (Очевидно, что в реальном мире я хотел бы создать не просто тупые прокси, такие как DepTest.double
, но некоторую библиотеку функций более высокого уровня, например multiplyByPow2(val : Outer#Inner, exponent: Int)
)