Почему 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. Вышеприведенное описание выполняется для случаев, когда это невозможно.