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

浪尽此生 提交于 2019-12-12 15:23:39

问题


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.


回答1:


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!




回答2:


PLEASE someone come up with a better solution. (Yes, a useEm equivalent could also be done in the public constructor, but this allows to call it again).

class SimIterator[S <: Simulator] private (
    val sim: Simulator,
    val useEm: () => Unit) {

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

  useEm()

}

And use it:

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

  val cm = CMH(mkCM[AnyRef])

  new SimIterator(this)(cm :: Nil)
}


来源:https://stackoverflow.com/questions/16244499/scala-re-use-generic-resulting-from-path-dependent-type-in-path-dependent-conte

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!