在 trait 中定义的 Scala 尾递归流处理器函数保留对流头的引用



在以下情况下

trait T {
 @tailrec
 def consume[A](as: Stream[A]): Unit = {
    if (as.isEmpty) ()
    else consume(as.tail)
  }
 }
object O extends T

调用O.consume(Range(1, N).toStream) N足够大,程序将耗尽内存,或者至少会消耗 O(N) 而不是所需的 O(1)。

为特征生成尾递归方法。特征扩展器中的方法条目(此处O)将调用转发给特征的方法,但在这样做时,它会保留对流头的引用。

因此,该方法是尾递归的,但仍然无法释放内存。补救措施:不要在特征中定义 Stream 函数,而直接在对象中定义。

另一种选择是 scalaz 的 EphemeralStream ,它保存对流头和尾的弱引用,并根据需要重新计算它们。

有一个简单的解决方法。只需将您的尾递归流使用者包装在另一个函数中,该函数通过 by-name 参数接收流:

import scala.annotation.tailrec
trait T {
  def consume[A](as: => Stream[A]): Unit = {
    @tailrec
    def loop[A](as: Stream[A]): Unit = {
        if (as.isEmpty) ()
       else loop(as.tail)
    }
    loop(as)
  }
}
object O extends T {
  def main(args: Array[String]): Unit = 
    O.consume(Range(1, 1000000000).toStream) 
}

转发器方法将保存对函数的引用,该函数计算表达式的结果是流:

public final class O$ implements T {
  public static final MODULE$;
  // This is the forwarder:
  public <A> void consume(Function0<Stream<A>> as) {
    T.class.consume(this, as);
  }
     .  .  .      
  public void main(String[] args) {
    consume(new AbstractFunction0() {
      public final Stream<Object> apply() {
        return package..MODULE$.Range().apply(1, 1000000000).toStream();
      }
    });
  }
}

相关内容

  • 没有找到相关文章

最新更新