因为我正在定义一个包含很多变量的解释器,所以我写这个:
type Context = Map[String, Int]
abstract class Expr
case class Let(varname: String, varvalue: Expr, body: Expr) extends Expr
case class Var(name: String) extends Expr
case class Plus(a: Expr, b: Expr) extends Expr
case class Num(i: Int) extends Expr
def eval(expr: Expr)(implicit ctx: Context): Int = expr match {
case Let(i, e, b) => eval(b)(ctx + (i -> eval(e)))
case Var(s) => ctx(s)
case Num(i) => i
case Plus(a, b) => eval(a) + eval(b)
}
对于很长的表达式,由于StackOverflowException
,对于类型为
Let("a", 1,
Let("b", Plus("a", "a"),
Let("c", Plus("b", "a"),
Let("d", 1, ... )
但是,一旦定义了变量的值,我只需要在Let
的主体上再次调用赋值器,在我看来它应该只进行某种部分尾递归。
如何在Scala中实现部分尾递归?
答案 0 :(得分:1)
您想要某种方式只在eval
的某些分支上进行尾调用优化。我不认为这是可能的 - 大多数Scala会做的是接受整个方法的@tailrec
注释,如果它不能将方法优化为循环,则在编译时失败。
然而,使用Let
进行尾调用这个迭代非常简单:
def eval(expr: Expr, ctx: Context): Int = {
// The expression/context pair we try to reduce at every loop iteration
var exprM = expr;
var ctxM = ctx;
while (true) {
expr match {
case Var(s) => return ctxM(s)
case Num(i) => return i
case Plus(a, b) => return eval(a,ctxM) + eval(b,ctxM)
case Let(i, e, b) => {
ctxM += i -> eval(e,ctxM). // Update ctxM
exprM = b // Update exprM
}
}
}
return 0; // unreachable, but Scala complains otherwise I'm not returning 'Int'
}
注意,由于Plus
s的长链,这不会解决堆栈溢出问题 - 由于递归调用不在尾部位置,因此我们无法做很多事情。 / p>
曾经有一段时间我认为Scala会做一些@tailcall
注释来处理这类事情,但我不确定对这类事情有多大兴趣。
答案 1 :(得分:0)
您应该避免在Scala中使用return
。在这种情况下,您可以为while控件使用一个标志。
例如
var result = Option.empty[Int]
while (result.isEmpty) {
...
result = ctxM(s)
...
}
result
还有其他(更好的IMO)方法可以解决此问题。例如https://typelevel.org/cats/datatypes/freemonad.html