递归函数中的多态scala返回类型

时间:2013-11-18 22:37:14

标签: scala recursion type-systems

我有一个树形结构:

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。但我找不到一种方法将它应用于像树一样的递归结构!有什么想法吗?

2 个答案:

答案 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.

此解决方案基于以下事实:方法的返回类型是协变的,因此scaleNodeLeaf的实现中的返回类型可以是scale中抽象方法Tree的返回类型。

答案 1 :(得分:1)

除了OOP和覆盖之外,还有另一个解决方案:

您实际上可以定义一个类型类Scaler,它代表从(T, Int)T的函数,其中TTree的子类型。在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
}

编辑:作为旁注:如果你有一个树不变的像所有右侧叶子大于或等于左侧的叶子,你可能想要考虑实施规模在树上的映射,从而有效地创建一个新的树,因为用负数进行缩放可能会违反不变量。