Scala 部分尾递归
Scala partial 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)
}
对于非常长的表达式,由于 WhosebugException
而失败,对于类型的表达式:
Let("a", 1,
Let("b", Plus("a", "a"),
Let("c", Plus("b", "a"),
Let("d", 1, ... )
但是,一旦定义了变量的值,我只需要在 Let
的主体上再次调用求值器,在我看来它应该只做一些部分 tail-recursion.
Scala 中如何实现部分尾递归?
您需要某种方法来仅对 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 的长链而导致的堆栈溢出问题 - 我们真的没有太多可以做的,因为递归调用不在尾部位置。
曾经有一段时间我认为 Scala 会做一些 @tailcall
注释来处理这种事情,但我不确定人们是否对这种事情有那么大的兴趣。
你应该避免在 Scala 中使用 return
。在这种情况下,您可以为 while 控件使用一个标志。
例如
var result = Option.empty[Int]
while (result.isEmpty) {
...
result = ctxM(s)
...
}
result
还有其他(IMO 更好)方法可以解决这个问题。例如https://typelevel.org/cats/datatypes/freemonad.html
因为我正在定义一个有很多变量的解释器,所以我这样写:
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)
}
对于非常长的表达式,由于 WhosebugException
而失败,对于类型的表达式:
Let("a", 1,
Let("b", Plus("a", "a"),
Let("c", Plus("b", "a"),
Let("d", 1, ... )
但是,一旦定义了变量的值,我只需要在 Let
的主体上再次调用求值器,在我看来它应该只做一些部分 tail-recursion.
Scala 中如何实现部分尾递归?
您需要某种方法来仅对 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 的长链而导致的堆栈溢出问题 - 我们真的没有太多可以做的,因为递归调用不在尾部位置。
曾经有一段时间我认为 Scala 会做一些 @tailcall
注释来处理这种事情,但我不确定人们是否对这种事情有那么大的兴趣。
你应该避免在 Scala 中使用 return
。在这种情况下,您可以为 while 控件使用一个标志。
例如
var result = Option.empty[Int]
while (result.isEmpty) {
...
result = ctxM(s)
...
}
result
还有其他(IMO 更好)方法可以解决这个问题。例如https://typelevel.org/cats/datatypes/freemonad.html