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

Posted

技术标签:

【中文标题】在 trait 中定义的 Scala 尾递归流处理器函数持有对流头的引用【英文标题】:Scala tail-recursive Stream processor function defined in trait holds reference to stream-head 【发布时间】:2012-09-11 07:01:34 【问题描述】:

以下情况

trait T 

 @tailrec
 def consume[A](as: Stream[A]): Unit = 
    if (as.isEmpty) ()
    else consume(as.tail)
  
 

object O extends T

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

【问题讨论】:

另见***.com/questions/12529697/… 【参考方案1】:

有一个简单的解决方法。只需将您的尾递归流消费者包装在另一个通过名称参数接收流的函数中:

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();
      
    );
  

【讨论】:

【参考方案2】:

为特征生成尾递归方法。 trait 扩展器中的方法入口(此处为 O)将调用转发到 trait 的方法,但在这样做的同时,它会保留对 Stream 头部的引用。

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

另一种方法是 scalaz 的 EphemeralStream,它持有对流头部和尾部的弱引用,并根据需要重新计算它们。

【讨论】:

EphemeralStream

以上是关于在 trait 中定义的 Scala 尾递归流处理器函数持有对流头的引用的主要内容,如果未能解决你的问题,请参考以下文章

从示例逐渐理解Scala尾递归

Scala尾递归优化

Scala-Trait:混入与多态

如何在 Scala 中使用 Stream.cons 编写不泄漏的尾递归函数?

scala-trait实现AOP编程

Spark基础-scala学习