Как написать непротекающую хвостовую рекурсивную функцию, используя 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)

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