Как написать непротекающую хвостовую рекурсивную функцию, используя Stream.cons в Scala?

При написании функции, работающей наStream(s), есть разные понятия рекурсии. Первый простой смысл не является рекурсивным на уровне компилятора, поскольку хвост, если не вычисляется мгновенно, поэтому функция возвращается немедленно, но возвращаемый поток является рекурсивным:

final def simpleRec[A](as: Stream[A]): Stream[B] = 
  if (a.isEmpty) Stream.empty              
  else someB(a.head) #:: simpleRec(a.tail) 

Вышеупомянутое понятие рекурсии не вызывает никаких проблем. Второй действительно хвост рекурсивен на уровне компилятора:

@tailrec
final def rec[A](as: Stream[A]): Stream[B] = 
  if (a.isEmpty) Stream.empty              // A) degenerated
  else if (someCond) rec(a.tail)           // B) tail recursion
  else someB(a.head) #:: rec(a.tail)       // C) degenerated

Проблема в том, чтоC) регистр обнаруживает компилятор как не-tailrec-вызов, даже если фактический вызов не выполняется. Этого можно избежать, выделив хвост потока в вспомогательную функцию:

@tailrec
final def rec[A](as: Stream[A]): Stream[B] = 
  if (a.isEmpty) Stream.empty              
  else if (someCond) rec(a.tail)          // B)
  else someB(a.head) #:: recHelp(a.tail)  

@tailrec
final def recHelp[A](as: Stream[A]): Stream[B] = 
  rec(as)

В то время как это компилируется, этот подход в конечном счете приводит к утечке памяти. Так как хвост рекурсивныйrec в конечном итоге вызывается изrecHelp функция, кадр стекаrecHelp Функция содержит ссылку на напор пара и не позволяет собирать поток мусора до тех пор, покаrec возврат вызовов, который может быть довольно продолжительным (с точки зрения шагов рекурсии) в зависимости от количества вызовов наB).

Обратите внимание, что даже в случае без помощника, если компилятор разрешил @tailrec, утечка памяти все еще может присутствовать, поскольку ленивый хвост потока фактически создает анонимный объект, содержащий ссылку на заголовок потока.

Ответы на вопрос(2)

Ваш ответ на вопрос