Search code examples
scalatype-inferencetypechecking

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]

Solution

  • I've managed to find a solution to my problem which I think is better than @PH88's proposal. I removed the constraints from SelfType/TransposedType and used ST forSome { type ST[+A2] } syntax in ++ argument type, like in the description of https://issues.scala-lang.org/browse/SI-8039

    Side effects of this solution:

    1. Can't define method implementations in GenTableLike in terms of other methods (since they will return a SelfType of an unknown nature) - i.e. can't reuse methods inside GenTableLike code. This can be workarounded by providing additional inheritance layer below GenTableLike and above KeyTable and IndexedTable
    2. Subclasses may define non-tables as their self/transposed types, but that doesn't seem like a big issue - there's no reason for them to do so.

    Resulting code:

    trait GenTableLike[RKT,
                       CKT,
                       +A,
                       +SelfType[+A2],
                       +TransposedType[+A2]] {
      def ++[B >: A](that: GenTableLike[RKT,
                                        CKT,
                                        B,
                                        ST forSome { type ST[+A2] },
                                        TT forSome { type TT[+A2] }]): SelfType[B] = ???
    }
    
    // Common ancestor for all table classes
    trait GenTable[RKT, CKT, +A]
      extends GenTableLike[RKT,
                           CKT,
                           A,
                           GenTable.Curried[RKT, CKT]#Self,
                           GenTable.Curried[CKT, RKT]#Self] {
      // Here we can implement common methods reusing other methods due to proper SelfType bounds
    }
    
    object GenTable {
      /** Type helper for defining self-recursive type */
      private type Curried[RKT, CKT] = {
        type Self[+A] = GenTable[RKT, CKT, A]
      }
    }
    
    trait KeyTable[RKT, CKT, +A]
      extends GenTable[RKT, CKT, A]
      with 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] {
      override def ++[B >: A](that: GenTableLike[RKT,
                                                 CKT,
                                                 B,
                                                 ST forSome { type ST[+A2] },
                                                 TT forSome { type TT[+A2] }]): MapKeyTable[RKT, CKT, B] = {
        new MapKeyTable
      }
    }
    
    object MapKeyTable {
      /** Type helper for defining self-recursive type */
      type Curried[RKT, CKT] = {
        type Self[+A] = MapKeyTable[RKT, CKT, A]
      }
    }
    
    trait IndexedTable[+A]
      extends GenTable[Int, Int, A]
      with GenTableLike[Int,
                        Int,
                        A,
                        IndexedTable,
                        IndexedTable]
    
    class IndexedSeqTable[+A]
        extends IndexedTable[A]
        with GenTableLike[Int,
                          Int,
                          A,
                          IndexedSeqTable,
                          IndexedSeqTable] {
      override def ++[B >: A](that: GenTableLike[Int,
                                                 Int,
                                                 B,
                                                 ST forSome { type ST[+A2] },
                                                 TT forSome { type TT[+A2] }]): IndexedSeqTable[B] = {
        new IndexedSeqTable
      }
    }
    
    
    // Usage
    
    def s1: IndexedSeqTable[Int] = ???
    def s2: IndexedSeqTable[Any] = ???
    
    def t1: MapKeyTable[Int, Int, Int] = ???
    def t2: MapKeyTable[Int, Int, Any] = ???
    
    // All of this works with result being of proper type
    t1.++[Any](t2)
    t1 ++ t2
    s1 ++ s2
    t1 ++ s1
    s1 ++ t1
    s2 ++ t1