我有一个树形结构:
sealed trait Tree
case class Node(l: Tree, r: Tree) extends Tree
case class Leaf(n: Int) extends Tree
修改树的函数:
def scale(n: Int, tree: Tree): Tree = tree match {
case l: Leaf => Leaf(l.n * n)
case Node(l, r) => Node(scale(n, l), scale(n, r))
}
上述方法的签名应该是什么,以便返回Tree的相应子类型,并使以下行编译?
scale(100, Leaf(1)).n // DOES NOT COMPILE
我到目前为止找到的最接近的答案是here,并谈到F-Bounded Quantification。但我找不到一种方法将它应用于像树一样的递归结构!有什么想法吗?
答案 0 :(得分:2)
在scala中,通常有两个选项来定义类型的函数。一个更具功能性(像你一样使用模式匹配),另一个更面向对象。
不幸的是,对于具有模式匹配的版本,我没有解决方案(尽管我会感兴趣)。但是我有更多面向对象版本的解决方案,如果这是一个选项:
sealed trait Tree {
def scale(n: Int): Tree
}
case class Node(l: Tree, r: Tree) extends Tree {
def scale(n: Int): Node = Node(l.scale(n), r.scale(n))
}
case class Leaf(n: Int) extends Tree {
def scale(m: Int): Leaf = Leaf(n * m)
}
Leaf(1).scale(100).n // does compile.
此解决方案基于以下事实:方法的返回类型是协变的,因此scale
和Node
中Leaf
的实现中的返回类型可以是scale
中抽象方法Tree
的返回类型。
答案 1 :(得分:1)
除了OOP和覆盖之外,还有另一个解决方案:
您实际上可以定义一个类型类Scaler
,它代表从(T, Int)
到T
的函数,其中T
是Tree
的子类型。在Scaler
的伴随对象中,您可以定义将执行实际工作的相应含义。返回值的静态类型来自类型类的定义。请注意,我使用两个通用参数扩展了Node
的定义。可以使用抽象类型替代。这留给读者一个练习:)
嗯,我们走了:
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
}
编辑:作为旁注:如果你有一个树不变的像所有右侧叶子大于或等于左侧的叶子,你可能想要考虑实施规模在树上的映射,从而有效地创建一个新的树,因为用负数进行缩放可能会违反不变量。