Почему foldRight и reduceRight НЕ являются хвостовыми рекурсивными?

Почему компилятор не переводит Scala

(1,2,3,4,5,6).foldRight(10)(_ * _)

эквивалент Java

final int[] intArray = new int[]{1,2,3,4,5,6};
int accumulator = 10;

for(int i = intArray.legth - 1; i >=0; i--) {
  accumulator = intArray[i] * accumulator;
}

Вопрос: почему foldLeft и reduceLeft являются хвостовыми рекурсивными, но их правые счета не являются?

Вот ссылки, в которых говорится, что правые не являются хвостовыми рекурсивными. Я спрашиваю, почему это так.

Откуда вы знаете, когда использовать fold-left и когда использовать fold-right?

Последствия foldr vs. foldl (или foldl ')

http://programming-scala.labs.oreilly.com/ch08.html

Ответы

Ответ 1

(1, 2, 3, 4, 5, 6) является 6-значным кортежем, который не имеет foldRight, но Array(1, 2, 3, 4, 5, 6) делает.

ArrayLike - это подклассы с подклассифицированными индексами с эффективным доступом к элементу, что означает оптимизацию определенных методов, включая, например, foldRight. Каждый массив неявно преобразуется в подкласс признака ArrayLike. Из Scala туловища:

  @tailrec
  private def foldr[B](start: Int, end: Int, z: B, op: (A, B) => B): B =
    if (start == end) z
    else foldr(start, end - 1, op(this(end - 1), z), op)

Bytecode:

private static java.lang.Object foldr(scala.collection.IndexedSeqOptimized, int, int, java.lang.Object, scala.Function2);

...

  Code:
   Stack=6, Locals=6, Args_size=5
   0:   iload_1
   1:   iload_2
   2:   if_icmpne   7
   5:   aload_3
   6:   areturn
   7:   aload_0
   8:   iload_2
   9:   iconst_1
   10:  isub
   11:  aload   4
   13:  aload_0
   14:  iload_2
   15:  iconst_1
   16:  isub
   17:  invokeinterface #21,  2; //InterfaceMethod scala/collection/SeqLike.apply:(I)Ljava/lang/Object;
   22:  aload_3
   23:  invokeinterface #72,  3; //InterfaceMethod scala/Function2.apply:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
   28:  astore_3
   29:  istore_2
   30:  astore_0
   31:  goto    0
  LineNumberTable: 
   line 68: 0
   line 67: 6
   line 69: 7

EDIT: метод в байт-коде является итеративным, что означает, что компилятор должен был применить оптимизацию хвостового вызова.

Без эффективного доступа к элементу (т.е. эффективный метод apply) наилучшим в общем случае может быть использование итераторов и нерекурсивная функция без хвоста для реализации foldRight или реверсирование коллекции путем создания нового и выполнения foldLeft на этом (последнее делается, в настоящее время). В случае всех последовательностей с эффективным случайным доступом это поведение переопределяется и оптимизируется.

Ответ 2

Это вопрос о том, как складывается складка. Операция foldLeft упорядочивает

Seq(1, 2, 3).foldLeft(10)(_ - _)

а

(((10 - 1) - 2) - 3)

(который равен 4), а foldRight -

Seq(1, 2, 3).foldRight(10)(_ - _)

а

(1 - (2 - (3 - 10)))

(что -8).

Теперь представьте, что вы вытаскиваете цифры 1, 2 и 3 из сумки и делаете расчет карандашом на бумаге.

В случае foldRight вы должны сделать это следующим образом:

  • Вытяните номер n из сумки
  • Напишите "n -?" на бумаге
  • Если в сумке остались цифры, вытащите еще один из мешка, иначе перейдите к 6.
  • Удалите знак вопроса и замените его на "(n -?)"
  • Повторить с 3.
  • Удалите знак вопроса и замените его на 10
  • Выполните расчет

В случае foldLeft вы можете сделать это следующим образом:

  • Напишите 10 на бумаге
  • Если в сумке остались цифры, вытащите еще один из мешка, иначе перейдите к 5.
  • Напишите "-n" рядом с выражением, которое у вас уже есть
  • Повторите с 2.
  • Выполните расчет

но вы этого не сделаете, потому что вы также можете сделать это следующим образом:

  • Напишите 10 на бумаге
  • Вытяните номер n из сумки
  • Вычтите n из значения, которое у вас есть, удалите значение и запишите новое значение
  • Повторите с 2.

Независимо от количества номеров в сумке, вам нужно иметь только одно значение, написанное на бумаге. Устранение хвостового вызова (TCE) означает, что вместо создания большой структуры рекурсивных вызовов в стеке вы можете выскочить и заменить накопленное значение по мере продвижения. (I.e., рекурсивно выраженное вычисление по существу выполняется итеративным образом.)

Как отмечали другие, структура с произвольным доступом, такая как ArrayLike, позволяет foldRight быть перегруппирована в операцию foldLeft и, следовательно, иметь право на TCE. Вышеприведенное описание выполняется для случаев, когда это невозможно.