Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inheritance and self-recursive type inference

(Scala 2.11.8)

I have a trait GenTableLike with complex self-recursive type signature which defines method ++ for concatenating compatible table implementations. I also have a hierarchy GenTableLike >: KeyTable >: MapKeyTable. However, ++'ing two MapKeyTable fails to infer self-recursive types.

Here's somewhat simplified snippet in which problem still reproduces:

trait GenTableLike[RKT,
                   CKT,
                   +A,
                   +SelfType[+A2] <: GenTableLike[RKT, CKT, A2, SelfType, TransposedType],
                   +TransposedType[+A2] <: GenTableLike[CKT, RKT, A2, TransposedType, SelfType]] {
  def ++[B >: A,
         T2 <: GenTableLike[RKT, CKT, B, ST, TT],
         ST[+A2] <: GenTableLike[RKT, CKT, A2, ST, TT],
         TT[+A2] <: GenTableLike[CKT, RKT, A2, TT, ST]](that: T2): SelfType[B] = ???
}

trait KeyTable[RKT, CKT, +A]
  extends GenTableLike[RKT,
                       CKT,
                       A,
                       KeyTable.Curried[RKT, CKT]#Self,
                       KeyTable.Curried[CKT, RKT]#Self]

object KeyTable {
  /** Type helper for defining self-recursive type */
  type Curried[RKT, CKT] = {
    type Self[+A] = KeyTable[RKT, CKT, A]
  }
}

class MapKeyTable[RKT, CKT, +A]
  extends KeyTable[RKT, CKT, A]
  with GenTableLike[RKT,
                    CKT,
                    A,
                    MapKeyTable.Curried[RKT, CKT]#Self,
                    MapKeyTable.Curried[CKT, RKT]#Self]

object MapKeyTable {
  /** Type helper for defining self-recursive type */
  type Curried[RKT, CKT] = {
    type Self[+A] = MapKeyTable[RKT, CKT, A]
  }
}

val t1: MapKeyTable[Int, String, Int] = ???
val t2: MapKeyTable[Int, String, Any] = ???

// The following works
t1.++[Any, MapKeyTable[Int, String, Any], ({ type ST[+A2] = MapKeyTable[Int, String, A2] })#ST, ({ type TT[+A2] = MapKeyTable[String, Int, A2] })#TT](t2)
t1.++[Any, MapKeyTable[Int, String, Any], MapKeyTable.Curried[Int, String]#Self, MapKeyTable.Curried[String, Int]#Self](t2)
// Error: inferred type arguments [Int,MapKeyTable[Int,String,Any],Nothing,Nothing] do not conform to method ++'s type parameter bounds [B >: Int,T2 <: GenTableLike[Int,String,B,ST,TT],ST[+A2] <: GenTableLike[Int,String,A2,ST,TT],TT[+A2] <: GenTableLike[String,Int,A2,TT,ST]]
t1 ++ t2

Am I doing something wrong here?

Notes:

Self-type and transposed type are used to define function return types. I also have an IndexedTable implementation defined as follows, so I can't rework self-type to accept 3 arguments

trait IndexedTable[+A]
    extends GenTableLike[Int,
                         Int,
                         A,
                         IndexedTable,
                         IndexedTable]

class IndexedSeqTable[+A]
    extends IndexedTable[A]
    with GenTableLike[Int,
                      Int,
                      A,
                      IndexedSeqTable,
                      IndexedSeqTable]
like image 894
Alex Abdugafarov Avatar asked Dec 21 '25 11:12

Alex Abdugafarov


1 Answers

How about turning the SelfType and TransposeType into abstract types? This is simpler, and working:

import scala.language.higherKinds

trait GenTableLike[RKT, CKT, +A] {
  type SelfType[+A2] <: GenTableLike[RKT, CKT, A2]
  type TransposedType[+A2] <: GenTableLike[CKT, RKT, A2]

  def ++[B >: A](that: GenTableLike[RKT, CKT, B]): SelfType[B]
}

trait KeyTable[RKT, CKT, +A]
  extends GenTableLike[RKT, CKT, A] {
  override type SelfType[+A2] <: KeyTable[RKT, CKT, A2]
  override type TransposedType[+A2] <: KeyTable[CKT, RKT, A2]
}

class MapKeyTable[RKT, CKT, +A]
  extends KeyTable[RKT, CKT, A] {
  override type SelfType[+A2] = MapKeyTable[RKT, CKT, A2]
  override type TransposedType[+A2] = MapKeyTable[CKT, RKT, A2]

  override def ++[B >: A](that: GenTableLike[RKT, CKT, B]): MapKeyTable[RKT, CKT, B] =
    new MapKeyTable[RKT, CKT, B]
}

val t1 = new MapKeyTable[Int, String, Int]
val t2 = new MapKeyTable[Int, String, Any]

// The following works
t1.++[Any](t2)
t1 ++ t2


trait IndexedTable[+A]
  extends GenTableLike[Int, Int, A] {
  override type SelfType[+A2] <: IndexedTable[A2]
  override type TransposedType[+A2] <: IndexedTable[A2]
}

class IndexedSeqTable[+A]
  extends IndexedTable[A] {
  override type SelfType[+A2] = IndexedSeqTable[A2]
  override type TransposedType[+A2] = IndexedSeqTable[A2]

  override def ++[B >: A](that: GenTableLike[Int, Int, B]): IndexedSeqTable[B] = new IndexedSeqTable[B]
}

Updated on Feb 15 in response to Alex's comment:

Recursive type + inheritance is tricky and I always avoid it if possible :-).
If SelfType and TransposeType are use as function return types only, how about eliminate the SelfType and TransposeType altogether and use implicit builder like:

import scala.language.higherKinds

trait GenTableLike[RKT, CKT, +A] {

  def value: A

  def ++[B >: A](that: GenTableLike[RKT, CKT, B])(implicit builder: GenTableLike.Builder[this.type, B]): builder.Self = {
    builder.buildSelf(that.value)
  }

  def transpose(implicit builder: GenTableLike.Builder[this.type, A]) = builder.buildTranspose(value)

}

object GenTableLike {

  trait Builder[-This, -A] {
    type Self
    type Transpose

    def buildSelf(a: A): Self

    def buildTranspose(a: A): Transpose
  }

}

trait KeyTable[RKT, CKT, +A]
  extends GenTableLike[RKT, CKT, A] {
}

class MapKeyTable[RKT, CKT, +A](override val value: A)
  extends KeyTable[RKT, CKT, A] {
}

object MapKeyTable {

  implicit def builder[RKT, CKT, A] = new GenTableLike.Builder[MapKeyTable[RKT, CKT, A], A] {
    override type Self = MapKeyTable[RKT, CKT, A]
    override type Transpose = MapKeyTable[CKT, RKT, A]

    override def buildSelf(a: A): Self = new MapKeyTable[RKT, CKT, A](a)

    override def buildTranspose(a: A): Transpose = new MapKeyTable[CKT, RKT, A](a)
  }

}

class MapKeyTableEx[RKT, CKT, +A](override val value: A)
  extends MapKeyTable[RKT, CKT, A](value)

object MapKeyTableEx {
  implicit def builder[RKT, CKT, A] = new GenTableLike.Builder[MapKeyTableEx[RKT, CKT, A], A] {
    override type Self = MapKeyTableEx[RKT, CKT, A]
    override type Transpose = MapKeyTableEx[CKT, RKT, A]

    override def buildSelf(a: A): Self = new MapKeyTableEx[RKT, CKT, A](a)

    override def buildTranspose(a: A): Transpose = new MapKeyTableEx[CKT, RKT, A](a)
  }

}


val t1 = new MapKeyTable[Int, String, Int](1)
val t2 = new MapKeyTable[Int, String, Any]("b")
val t3 = new MapKeyTableEx[Int, String, Int](1)
val t4 = new MapKeyTableEx[Int, String, Any]("b")

// The following works
t1 ++ t2
t1 ++ t3
t2 ++ t3
t3 ++ t4

t1.transpose
t2.transpose
t3.transpose
t4.transpose
like image 194
PH88 Avatar answered Dec 23 '25 03:12

PH88



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!