这是楼梯书中的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子类)。此外,我可能需要几个类似的转换。有没有更好的选择(可能使用更高阶函数)?
答案 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
)这样的函数。