Is it possible to use continuations to make foldRight tail recursive?

前端 未结 4 377
臣服心动
臣服心动 2021-01-02 07:34

The following blog article shows how in F# foldBack can be made tail recursive using continuation passing style.

In Scala this would mean that:

4条回答
  •  时光取名叫无心
    2021-01-02 07:50

    I'm late to this question, but I wanted to show how you can write a tail-recursive FoldRight without using a full trampoline; by accumulating a list of continuations (instead of having them call each other when done, which leads to a stack overflow) and folding over them at the end, kind of like keeping a stack, but on the heap:

    object FoldRight {
    
      def apply[A, B](list: Seq[A])(init: B)(f: (A, B) => B): B = {
        @scala.annotation.tailrec
        def step(current: Seq[A], conts: List[B => B]): B = current match {
          case Seq(last) => conts.foldLeft(f(last, init)) { (acc, next) => next(acc) }
          case Seq(x, xs @ _*) => step(xs, { acc: B => f(x, acc) } +: conts)
          case Nil => init
        }
        step(list, Nil)
      }
    
    }
    

    The fold that happens at the end is itself tail-recursive. Try it out in ScalaFiddle

    In terms of performance, it performs slightly worse than the tail call version.

    [info] Benchmark            (length)  Mode  Cnt   Score    Error  Units
    [info] FoldRight.conts           100  avgt   30   0.003 ±  0.001  ms/op
    [info] FoldRight.conts         10000  avgt   30   0.197 ±  0.004  ms/op
    [info] FoldRight.conts       1000000  avgt   30  77.292 ±  9.327  ms/op
    [info] FoldRight.standard        100  avgt   30   0.002 ±  0.001  ms/op
    [info] FoldRight.standard      10000  avgt   30   0.154 ±  0.036  ms/op
    [info] FoldRight.standard    1000000  avgt   30  18.796 ±  0.551  ms/op
    [info] FoldRight.tailCalls       100  avgt   30   0.002 ±  0.001  ms/op
    [info] FoldRight.tailCalls     10000  avgt   30   0.176 ±  0.004  ms/op
    [info] FoldRight.tailCalls   1000000  avgt   30  33.525 ±  1.041  ms/op
    

提交回复
热议问题