Reputation: 14842
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.
Upvotes: 4
Views: 470
Reputation: 9862
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!
Upvotes: 2
Reputation: 14842
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)
}
Upvotes: 0