Scala部分尾递归

时间:2016-11-25 17:44:45

标签: scala recursion stack-overflow tail-recursion

因为我正在定义一个包含很多变量的解释器,所以我写这个:

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中实现部分尾递归?

2 个答案:

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