SergGr
SergGr

Reputation: 23788

Dependent type with reference back (Scala)

I'm playing with (path-)dependent types in Scala and stumbled upon a following scenario that I can't find a good solution for. Assume that I want to have some hierarchy of dependent types and I want each of them to have a reference back to its "owner" object. I want this back-reference to be able to call some methods on the correct "owner" objects. What is the correct way to do it?

Here is a small example. There is a "base" trait Outer with a dependent type Inner. The base Outer trait defines some method double that works on the dependent type. There is also a specific class ConcreteOuter with a specific dependent class ConcreteInner that uses simple Int for the value.

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)
}

So far so good. Now assume I'd like to be able to call that double method in a context where I have only an instance of some Inner class but not the corresponding Outer-instance. For example, let's try to create another double method that just calls the original Outer.double in some other (independent) context:

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)

}

This code compiles but requires a quite ugly hack of asDependent. If I use line #3 instead of line #4, the code doesn't compile. If I split the line #3 in a following way the code doesn't compile anymore

  def double(inner: Outer#Inner) = {
    val outer = inner.outer
    outer.double(inner.asDependent)
  }

Moreover, if I replace line #1 with line #2 even the asDependent hack stops working.

So it looks like somehow sometimes the compiler knows that the outer field of the Inner object and the "owner" object aka outerSelf is the same thing and sometimes it doesn't and it is not clear how to persuade the compiler when it doesn't recognize them as the same thing.

Is there a way to work this around? Or is this a totally wrong approach to my problem? (Obviously in the real world I'd like to create not just dumb proxies such as DepTest.double but some library of higher level functions such as multiplyByPow2(val : Outer#Inner, exponent: Int))

Upvotes: 1

Views: 125

Answers (1)

alamit
alamit

Reputation: 392

I am not very experienced with path-dependant types, but from what I can read from here, it seems that what happens is that Outer and outerSelf.type are not the same thing:

  • outerSelf.type consists of either outerSelf or null
  • outerSelf: Outer consists of only outerSelf

I think your problem is coming from here, but I have not enough knowledge on this to be able to help you more.

Upvotes: 0

Related Questions