Aside from OOP and overriding, here is another solution:
You can actually define a type-class Scaler
that represents a function from (T, Int)
to T
where T
is a subtype of Tree
. In the companion object of Scaler
you can then define the corresponding implicits which will do the actual work. The static type of the return value follows from the definition of the type-class. Note that I extended your definition of Node
with two generic parameters. There may be an alternative by using abstract types instead. This is left as an exercise to the reader :)
Well, here we go:
import language.implicitConversions
sealed trait Tree
case class Node[L <: Tree, R <: Tree](l: L, r: R) extends Tree
case class Leaf(n: Int) extends Tree
trait Scaler[T <: Tree] extends ((T, Int) => T)
object Scaler {
implicit object scalesLeafs extends Scaler[Leaf] {
def apply(l: Leaf, s: Int) =
Leaf(l.n * s)
}
implicit def scalesNodes[L <: Tree: Scaler, R <: Tree: Scaler] = new Scaler[Node[L,R]] {
val ls = implicitly[Scaler[L]]
val rs = implicitly[Scaler[R]]
def apply(n: Node[L,R], s: Int) =
Node(ls(n.l, s), rs(n.r, s))
}
}
object demo extends App {
def scale[T <: Tree](t: T, s: Int)(implicit ev: Scaler[T]): T =
ev(t, s)
val check1 = scale(Leaf(3), 5)
val check2 = scale(Node(Leaf(3), Leaf(7)), 5)
Console println check1.n
Console println check2.l.n
Console println check2.r.n
}
EDIT: As a sidenote: If you have a tree invariant like all right-hand side leafs are greater or equal than the ones on the left-hand side, you might want to consider implementing scale in terms of mapping over the tree and thereby effecively creating a new tree, because scaling with negative numbers could violate the invariant.