Search code examples
scalagenericspath-dependent-type

Scala: Re-use generic resulting from path-dependent type in path-dependent context


In short: The following does not compile (reason below), how can I make it work?

trait Simulator {
  type CM[T]
  def useCM(v: CM[_])
}

case class CMH[S <: Simulator,T](cm: S#CM[T])

class SimIterator[S <: Simulator](val sim: S, val cmhs: Seq[CMH[S,_]]) {
  cmhs foreach { cmh => sim.useCM(cmh.cm) }
  /*
  compile time error:
   type mismatch; found : cmh.cm.type (with underlying type S#CM[_$2]) required:
   SimIterator.this.sim.CM[_] Note: _$2 <: Any (and cmh.cm.type <: S#CM[_$2]),
   but type CM is invariant in type T. You may wish to define T as +T instead.
   (SLS 4.5)
  */
}

The idea behind the structure is that CMH hides T-specific behavior from SimIterator where as the latter handles common tasks. S is used to force vlaues in CMH to have the right type without having an instance of Simulator.

In the foreach, there seems to be a subtyping issue related to CM. If S#CM is a concrete type we need sim.CM =:= S#CM. However, look at the following:

object Test extends Simulator {
  type CM[T] = Option[T]
  def useCM(v: CM[_]) = println(v)
  def mkCM[T]: CM[T] = None

  CMH[Simulator,AnyRef](mkCM[AnyRef])
}

We have now a CMH that we can pass into a SimIterator together with any Simulator. So apparently the typing of SimIterator is not restrictive enough. How can express (and use) S =:= sim.type?

UPDATE

This works, but cannot be used in the constructor (illegal dependent method type: parameter appears in the type of another parameter in the same section or an earlier one)

class SimIterator(val sim: Simulator) {
  def doIt(cmhs: Seq[CMH[sim.type,_]]) {
    cmhs foreach { cmh => sim.useCM(cmh.cm) }
  }
}

The upper example works, but is not what I want. cmhs should be passed-in upon construction.


Solution

  • You can easily fix the issue by moving the abstract type member to a type parameter like this:

    trait Simulator[CM[_]] {
      def useCM(v: CM[_])
    }
    
    case class CMH[CM[_]](cm: CM[_])
    
    class SimIterator[S <: Simulator[CM], CM[_]](val sim: S, val cmhs: Seq[CMH[CM]]) {
      cmhs foreach { cmh => sim.useCM(cmh.cm) }
    }
    

    That way you avoid using the type projection, if you really need to keep CM as a type member please provide more detail explaining in which case you need a type projection and why.

    EDIT/UPDATE

    Welcome to the "Bakery of Doom"! ;-)

    There is two solution in this case:

    1st, put you Iterator into the cake, so you can access the type directly:

    case class CMH[CM[_]](cm: CM[_])
    
    trait Cake {
      type CM[_]
    
      trait Simulator {
        def useCM(v: CM[_])
      }
    
      class SimIterator[S <: Simulator](val sim: S, val cmhs: Seq[CMH[CM]]) {
        cmhs foreach { cmh => sim.useCM(cmh.cm) }
      }
    }
    

    or 2nd, encapuslate your Iterator in an other class, making possible to access the path-dependent type:

    trait Cake {
      type CM[_]
    
      trait Simulator {
        def useCM(v: CM[_])
      }
    }
    
    case class CMH[CM[_]](cm: CM[_])
    
    class SimIteratorBuilder[C <: Cake](val cake: Cake) {
      class SimIterator(val sim: cake.Simulator, val cmhs: Seq[CMH[cake.CM]]) {
        cmhs foreach { cmh => sim.useCM(cmh.cm) }
      }
    }
    

    Once you are in the cake, there is no way to escape it!