(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?
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]
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:
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
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