I am not sure whether the title of the question is phrased correctly in Scala type terminology... I am facing a problem that is somewhat analogous to what Scala collections manage with CanBuildFrom.
There is a hierarchy of higher-kinded types and a parallel hierarchy of containers (not necessarily collections) of those types. An operation that, say, filters the items in the container, should return the exact type of the container that the operation is executed on, with the items filtered.
The problem I am encountering is that I can't find the right approach to restricting the implicit evidence requirement to the exact type of container to be constructed without passing the container type explicitly.
A simplified version of the setup I'm dealing with is below. I'd appreciate some advice on how to approach this issue.
@implicitNotFound(msg = "Cannot construct a ${B} from ${A} with arguments of type ${Args}.")
trait CanConstruct[A, -Args, +B] {
def apply(args: Args): B
}
class A1
trait C1[A <: A1] {
def a: A
def active: Boolean
}
trait C1s[C <: C1[A] forSome {type A <: A1}] {
def values: Seq[C]
// @note something like def active[From <: C1s[_]]... will find implicits
// but will be happy to use a super-type's implicit for a subtype's
def active[To[_ <: C]](implicit builder: CanConstruct[To[_], Seq[C], To[C]]): To[C] =
builder(values.filter(_.active))
}
class C2(val a: A1, val active: Boolean) extends C1[A1]
class C2s[C <: C2](val values: Seq[C]) extends C1s[C]
object C2s {
class C2CanConstruct[C <: C2] extends CanConstruct[C2s[_], Seq[C], C2s[C]] {
def apply(args: Seq[C]): C2s[C] = new C2s(args)
}
implicit def C2CanConstruct[C <: C2] = new C2CanConstruct[C]
}
class C3s[C <: C2](values: Seq[C]) extends C2s[C](values)
object C3s {
class C3CanConstruct[C <: C2] extends CanConstruct[C3s[_], Seq[C], C3s[C]] {
def apply(args: Seq[C]): C3s[C] = new C3s(args)
}
implicit def C3CanConstruct[C <: C2] = new C3CanConstruct[C]
}
val a = new A1()
val cs = Seq(new C2(a, true), new C2(a, false))
// How can active pick the corresponding C?CanConstruct implicit automatically?
// i.e., new C2s(cs).active returns C2s[...] and
// new C3s(cs).active returns C3s[...]
new C2s(cs).active[C2s]
new C3s(cs).active[C3s]
Update:
The type member approach in the answer below is promising, but fails to handle the case where C1 has type parameters (Scastie). I omitted that detail in the original question formulation with the desire to strike a balance between necessary & unnecessary complexity. When it comes to the type member approach, it seems the additional complexity is necessary.