Search code examples
scalaparametric-polymorphism

scala: Referencing Trait with F-Bound Parameters


I am designing a F-Bound data type, and have a working companion object. I would like to reference this companion object from the trait itself, but I can't get the types right.

trait A[AA <: A[AA]] {
  self =>
  val data: String
}
case class A1(data : String) extends A[A1]

trait B[BB <: B[BB, AA], AA <: A[AA]] {
  self: BB =>
  val content: AA
  def companion: BComp[BB, AA]  // What is the correct type?
  def companion2: BComp2[BB, AA] // What is the correct type?
}
trait BComp[BB[X <: BB[X, AA], Y <: AA[Y]], AA[Y <: AA[Y]]]

trait BComp2[BB[X <: AA[X]], AA[X <: AA[X]]]

case class BInst[AA <: A[AA]](content: AA) extends B[BInst[AA], AA] {
  def companion = BInst
  def companion2 = BInst2
}

object BInst extends BComp[B, A]
object BInst2 extends BComp2[BInst, A]

A working solution for either companion or companion2 would suffice, although a general hint on how to construct these type signatures would be useful.

edit

I want to use the companion object to store canBuildFrom style implicits as well as Builders, but as the content type A has an upper bound, all generating functions need to be aware of this bounding, so hence the parametrization of the companion object trait. The inspiration for this design comes from GenericCompanion.scala, of course, adding the type bounds makes everything more difficult :P


Solution

  • The obstacle to any clear definition of types for companion is that BComp is parametrized by two parametrized types - while the first one is compatible with B, the second one AA[Y <: AA[Y]] is impossible to construct. So quite simply, we need to add this type to the parametrization of B:

    trait B[BB <: B[BB, AA, X],
            AA[T <: AA[T]] <: A[T], 
            X <: AA[X]]  {
        self: BB =>
        val content: X
        def companion: BComp[B, AA]
    }
    

    now we have a compatible type AA for our companion object (which needs only a small expansion):

    trait BComp[BHere[BB <: BHere[BB, AAA, Z],
                      AAA[Y <: AAA[Y]],
                      Z <: AAA[Z]],
                AA[Y <: AA[Y]]]
    

    Isn't that pretty?

    case class BInst[X <: A[X]](content: X) extends B[BInst[X], A, X] {
        def companion: BComp[B, A] = BInst5
      }
    object BInst extends BComp[B, A]
    

    companion2

    For companion2, we just need to change the first part of the parametrization of B, so that trait B and the companion trait become:

    trait B[BB[TS <: AA[TS]] <: B[BB, AA, TS],
            AA[T <: AA[T]] <: A[T], 
            X <: AA[X]] {
        self: BB[X] =>
        val content: X
        def companion2: BComp[BB, AA]
      }
    
    trait BComp[BHere[TS <: AA[TS]],
                AA[Y <: AA[Y]]]
    

    This is slightly more manageable. The case class and case objects are:

    case class BInst[X <: A[X]](content: X) extends B[BInst, A, X] {
      def companion2: BComp[BInst, A] = BInst5
    }
    object BInst extends BComp[BInst, A]
    

    If this is useful to anybody the rethink. I have changed my own design approach and I suggest you do too. Coming up with a solution to these types has now been a purely academic exercise!