Say I have a Scala trait that does some computation and then calls a polymorphic method on extending classes that might have a different method signature in each class:
trait GenericThing {
val vals: Map[String, Any]
def doGenericStuff(): Unit = {
println(f"do some other stuff here on ${vals}")
doSpecificStuff(vals)
}
// what should method signature be?
def doSpecificStuff(vals: Any*)
}
class SpecificThing extends GenericThing {
val vals = Map(
"count" -> 3,
"animal" -> "rabbit",
"weight" -> 9.5
)
// broken -- doesn't match superclass signature
def doSpecificStuff(count: Int, animal: String, weight: Double): Unit = {
println(f"${count} quick brown ${animal}s weigh ${weight * count} pounds")
}
}
I want the SpecificThing#doSpecificStuff
method to have a proper method signature like doSpecificStuff(count: Int, animal: String, weight: Double)
, not just a generic one like doSpecificStuff(vals: Any*)
. It may have a different arity in each implementor of the trait. (If it helps, the names of the parameters could be standardized: doSpecificStuff(a: Int, b: String, c: Double)
).
Is there a way, perhaps using Shapeless or something like it, to make something like this work?
Is that val vals: Map[String, Any]
specifically required?
How about parameterising the GenericThing
on input contract of doSpecificStuff
?
trait GenericThing[A] {
val value: A
def doGenericStuff(): Unit = {
println(f"do some other stuff here on ${value}")
doSpecificStuff(value)
}
def doSpecificStuff(value: A)
}
Specific Implementation:
class SpecificThing1(val value: SpecificThing1.Value) extends GenericThing[SpecificThing1.Value] {
override def doSpecificStuff(value: SpecificThing1.Value): Unit = {
println(f"${value.count} quick brown ${value.animal}s weigh ${value.weight * value.count} pounds")
}
}
object SpecificThing1 {
final case class Value(count: Int, animal: String, weight: Double)
}
Usage:
val specificThing = new SpecificThing1(SpecificThing1.Value(3, "rabbit", 9.5))
specificThing.doGenericStuff()
// do some other stuff here on SpecificValueThing(3,rabbit,9.5)
// 3 quick brown rabbits weigh 28.5 pounds
You can try
import shapeless.ops.maps.FromMap
import shapeless.{::, HList, HNil}
trait GenericThing {
def vals: Map[String, Any]
type L <: HList
def doGenericStuff()(implicit fromMap: FromMap[L]): Unit = {
println(f"do some other stuff here on ${vals}")
doSpecificStuff(fromMap(vals).getOrElse(???))
}
def doSpecificStuff(vals: L): Unit
}
class SpecificThing extends GenericThing {
override val vals = Map(
"count" -> 3,
"animal" -> "rabbit",
"weight" -> 9.5
)
override type L = Int :: String :: Double :: HNil
override def doSpecificStuff(vals: L): Unit = vals match {
case count :: animal :: weight :: HNil =>
println(f"${count} quick brown ${animal}s weigh ${weight * count} pounds")
}
}
In case this point has been missed in all the noise, this pattern simple doesn't work:
trait GenericThing {
def doSpecificStuff(vals: Any*)
}
class SpecificThing extends GenericThing {
def doSpecificStuff(count: Int, animal: String, weight: Double)
}
You cannot override a generic method with a more specific method, because you can call the generic method with arguments that the specific method does not accept.
More generally, it is not a good pattern to have a base class with lots of functionality that calls overridden methods on itself. A better model is to have the generic code delegate the non-generic behaviour to another class that is injected into the implementation, either directly or using a typeclass.
I'd use path-dependent types and value function for that
trait GenericThing {
type I
def doSpecificStuff(i: I): Unit
}
class SpecificThing extends GenericThing {
type I = (Int, String, Double) // better be case class
def doSpecificStuff(tupple: (Int, String, Double)): Unit
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.