【问题标题】:why scala doesn't make tail call optimization?为什么scala不进行尾调用优化?
【发布时间】:2013-05-08 12:23:17
【问题描述】:

只是玩延续。目标是创建一个函数,该函数将接收另一个函数作为参数,以及执行量 - 和返回函数,该函数将应用参数给定的次数。

实现看起来很明显

def n_times[T](func:T=>T,count:Int):T=>T = {
  @tailrec
  def n_times_cont(cnt:Int, continuation:T=>T):T=>T= cnt match {
        case _ if cnt < 1 => throw new IllegalArgumentException(s"count was wrong $count")
        case 1 => continuation
        case _ => n_times_cont(cnt-1,i=>continuation(func(i)))
      }
  n_times_cont(count, func)
}

def inc (x:Int) = x+1

    val res1 = n_times(inc,1000)(1)  // Works OK, returns 1001

val res = n_times(inc,10000000)(1) // FAILS

但没有问题 - 此代码因 StackOverflow 错误而失败。为什么这里没有尾调用优化?

我使用 Scala 插件在 Eclipse 中运行它,它返回 线程“主”java.lang.StackOverflowError 中的异常 在 scala.runtime.BoxesRunTime.boxToInteger(未知来源) 在 Task_Mult$$anonfun$1.apply(Task_Mult.scala:25) 在 Task_Mult$$anonfun$n_times_cont$1$1.apply(Task_Mult.scala:18)

附言

几乎是直接翻译的 F# 代码可以正常工作

let n_times_cnt func count = 
    let rec n_times_impl count' continuation = 
        match count' with
        | _ when count'<1 -> failwith "wrong count"
        | 1 -> continuation
        | _ -> n_times_impl (count'-1) (func >> continuation) 
    n_times_impl count func

let inc x = x+1
let res = (n_times_cnt inc 10000000) 1

printfn "%o" res

【问题讨论】:

标签: scala continuations tail-call-optimization


【解决方案1】:

Scala 标准库scala.util.control.TailCalls 中实现了蹦床。所以重新审视你的实现......当你使用continuation(func(t)) 构建嵌套调用时,这些是尾调用,只是没有被编译器优化。所以,让我们建立一个T =&gt; TailRec[T],其中堆栈帧将被堆中的对象替换。然后返回一个函数,该函数将接受参数并将其传递给该蹦床函数:

import util.control.TailCalls._
def n_times_trampolined[T](func: T => T, count: Int): T => T = {
  @annotation.tailrec
  def n_times_cont(cnt: Int, continuation: T => TailRec[T]): T => TailRec[T] = cnt match {
    case _ if cnt < 1 => throw new IllegalArgumentException(s"count was wrong $count")
    case 1 => continuation
    case _ => n_times_cont(cnt - 1, t => tailcall(continuation(func(t))))
  }
  val lifted : T => TailRec[T] = t => done(func(t))
  t => n_times_cont(count, lifted)(t).result
}

【讨论】:

    【解决方案2】:

    我在这里可能错了,但我怀疑n_times_cont 内部函数已正确转换为使用尾递归;罪魁祸首不在。

    堆栈被收集的continuation 闭包(即i=&gt;continuation(func(i)))炸毁,一旦您应用主函数的结果,就会对您的inc 方法进行10000000 次嵌套调用。

    其实你可以试试

    scala> val rs = n_times(inc, 1000000)
    rs: Int => Int = <function1> //<- we're happy here
    
    scala> rs(1) //<- this blows up the stack!
    

    顺便说一句,你可以重写

    i=&gt;continuation(func(i))

    作为

    continuation compose func

    为了更好的可读性

    【讨论】:

    • 据我所知,CPS 的整个想法就是这个闭包集合。但看起来 Scala 无法使这样的构造工作。 stackoverflow.com/questions/8549433/… p.s.谢谢你的作曲技巧,我不知道
    • 是的,您希望 i =&gt; ... lambda 对 continuation 进行尾调用,@tailrec 不会为您做任何事情,在 Scala 领域也不会做任何其他事情。我猜你可以建造自己的蹦床:)
    • @MyseriousDan,好吧,“Scala 领域的任何其他东西”可能是一个强有力的声明。标准库有util.control.TailCalls...
    • @huynhjl 哈哈,这或多或少是我建造自己的蹦床的意思:)
    猜你喜欢
    • 2012-01-04
    • 1970-01-01
    • 2019-12-26
    • 2021-12-16
    • 1970-01-01
    • 2012-12-14
    • 2015-01-18
    • 1970-01-01
    • 2017-05-15
    相关资源
    最近更新 更多