The design looks fine to me, except extending case classes is not recommended. A brief summary of the reasons why can be found here: https://stackoverflow.com/a/12705634/2186890
So you will might want to rewrite Feature
as something like this:
trait Feature { def value: String }
Now you can define case classes for pattern matching etc. like this:
case class CountedFeature(value: String, count: Long) extends Feature with Counted
There is no easy way to avoid combinatorial explosion of case classes like this, but you are enabled to use types such as Feature with Counted
wherever you like. Keep in mind that you can easily create objects that match type Feature with Counted
on the fly. For instance:
val x: Feature with Counted = new Feature with Counted { val value = ""; val count = 0L }
Implementing computeWeightsByCount
like you want is a little tricky, because there is no easy way to build a T with Weighted
without knowing more about type T
. But it can be done with implicit methods. Essentially, we need to have a defined path for generating a T with Weighted
from a T
for every Feature with Counted
that you want to apply this method to. For instance, we start with this:
trait Feature { def value: String }
trait Counted { def count: Long }
trait Weighted { def weight: Double }
trait Indexed { def index: Int }
We want to define computeWeightsByCount
like you did in your question, but also taking an implicit method that takes a T
and a weight, and produces a T with Weighted
:
def computeWeightsByCount[
T <: Feature with Counted](
features: List[T])(
implicit weighted: (T, Double) => T with Weighted
): List[T with Weighted] = {
def weight(fc: Feature with Counted): Double = 0.0d
features map { f => weighted(f, weight(f)) }
}
Now we need to define an implicit method to produce weighted features from the input features. Let's start with getting a Feature with Counted with Weighted
from a Feature with Counted
. We'll put it in companion object for Feature
:
object Feature {
implicit def weight(fc: Feature with Counted, weight: Double): Feature with Counted with Weighted = {
case class FCW(value: String, count: Long, weight: Double) extends Feature with Counted with Weighted
FCW(fc.value, fc.count, weight)
}
}
We can use it like so:
case class FC(value: String, count: Long) extends Feature with Counted
val fcs: List[Feature with Counted] = List(FC("0", 0L), FC("1", 1L))
val fcws: List[Feature with Counted with Weighted] = computeWeightsByCount[Feature with Counted](fcs)
For any type that you want to compute weighted counts for, you need to define a similar such implicit method.
Admittedly, this is far from a beautiful solution. So yes, you are right, you may want to rethink the design. The advantage to this approach, however, is that any further extensions to the Feature
"hierarchy" can be made without needing to make any changes to computeWeightsByCount
. Whoever writes the new trait can provide the appropriate implicit methods as well.