简体   繁体   中英

Can a make a Scala trait with a polymorphic, variable-arity abstract method?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM