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).

Ответы

Ответ 1

Метод хвоста-рекурсивный генерируется для признака. Запись метода в расширителе признака (здесь O) пересылает вызов методу признака, но при этом он сохраняет ссылку на заголовок потока.

Таким образом, метод является хвостовым рекурсивным, но память все еще не может быть выпущена. Устранение: не определяйте функции Stream в свойствах, непосредственно непосредственно в объектах.

Альтернативой является scalaz EphemeralStream, которая содержит слабые ссылки на головку и хвост потока и перепродает их по требованию.

Ответ 2

Существует обходное решение. Просто заверните своего рекурсивного потребителя потока в другую функцию, которая получает поток через параметр 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();
      }
    });
  }
}