案例树转型

时间:2012-04-25 04:54:05

标签: scala tree case transformation

这是楼梯书中的Expr课程。

abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String, left: Expr, right: Expr) extends Expr

现在,我想要一个函数来重命名表达式中的变量。这是我的第一次尝试。

def renameVar(expr: Expr, varName: String, newName: String): Expr = expr match {
    case Var(name) if name == varName => Var(newName)
    case Number(_) => expr
    case UnOp(operator, arg) => UnOp(operator, renameVar(arg, varName, newName))
    case BinOp(operator, left, right) => BinOp(operator, renameVar(left, varName, newName), renameVar(right, varName, newName))
}

val anExpr = BinOp("+", Number(1), Var("x"))
val anExpr2 = renameVar(anExpr, "x", "y")

这有效,但很繁琐(我正在使用的实际类有几个case子类)。此外,我可能需要几个类似的转换。有没有更好的选择(可能使用更高阶函数)?

1 个答案:

答案 0 :(得分:2)

所以你的renameVar版本必须知道两件事:它必须知道如何递归树,并且必须知道如何重命名变量。

一种解决方案可能是将这两个问题分开。您可以使用visitor design pattern让每个类控制它如何进行递归; visit方法只关注如何遍历树。当它遍历时,它可以通过一个处理实际工作的函数(在你的情况下重命名一个变量)。

这是一个简单的实现,它传递一个转换函数(在Expr上运行并返回Expr)。它使用PartialFunction这一事实允许您对树中的表达式进行模式匹配以进行操作。案例未涵盖的任何表达式都会回归到正常递归(由doVisit指定)。

根据不同任务的不同,您可能需要更复杂的访问方法。但这应该让你了解方向:

// Class Hierarchy
abstract class Expr {
  def visit(f: PartialFunction[Expr, Expr]): Expr = if (f.isDefinedAt(this)) f(this) else doVisit(f)
  protected def doVisit(f: PartialFunction[Expr, Expr]): Expr
}
case class Var(name: String) extends Expr {
  protected def doVisit(f: PartialFunction[Expr, Expr]) = this
}
case class Number(num: Double) extends Expr {
  protected def doVisit(f: PartialFunction[Expr, Expr]) = this
}
case class UnOp(operator: String, arg: Expr) extends Expr {
  protected def doVisit(f: PartialFunction[Expr, Expr]) = UnOp(operator, arg.visit(f))
}
case class BinOp(operator: String, left: Expr, right: Expr) extends Expr {
  protected def doVisit(f: PartialFunction[Expr, Expr]) = BinOp(operator, left.visit(f), right.visit(f))
}

// Transformation Functions
def renameVar(expr: Expr, varName: String, newName: String): Expr = {
  expr.visit { case Var(`varName`) => Var(newName) }
}

现在,您可以引入一个新类,例如TernaryOp(String, Expr, Expr, Expr),以类似的方式定义其doVisit方法,并且无需您修改renameVar(或任何其他转换)像renameVar)这样的函数。