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