Почему foldRight и reduRight НЕ являются хвостовой рекурсией?
Почему компилятор не переводит 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 и reduLeft являются хвостовой рекурсией, а их правые аналоги - нет?
Вот ссылки, которые говорят, что правша не хвостовая рекурсия. Я спрашиваю, почему это так.
Как вы знаете, когда использовать сложение влево и когда использовать сложение вправо?
2 ответа
(1, 2, 3, 4, 5, 6)
является 6-значным кортежем, который не имеет foldRight
, но Array(1, 2, 3, 4, 5, 6)
делает.
ArrayLike
является признаком индексируемых последовательностей с эффективным доступом к элементам, то есть оптимизированы определенные методы, в том числе, например, foldRight
, Каждый массив неявно преобразуется в подкласс ArrayLike
черта характера. Из багажника Скала:
@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
РЕДАКТИРОВАТЬ: метод в байт-коде является итеративным, это означает, что компилятор должен был применить оптимизацию хвостового вызова.
Без эффективного доступа к элементу (т.е. apply
метод) лучшее, что можно сделать в общем, это использовать итераторы и нехвостовую рекурсивную функцию для реализации foldRight
или перевернуть коллекцию, создав новую и выполнив foldLeft
на этом (последнее сделано, в настоящее время). В случае всех последовательностей с эффективным произвольным доступом это поведение переопределяется и оптимизируется.
Вопрос в том, как происходит сворачивание. 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 -?" на бумаге
- Если в сумке остались цифры, вытяните из сумки еще n, иначе перейдите к 6.
- Сотрите знак вопроса и замените его на "(n -?)"
- Повторите с 3.
- Сотрите знак вопроса и замените его на 10
- Выполнить расчет
в foldLeft
случай, вы можете сделать это так:
- Напишите 10 на бумаге
- Если в сумке остались цифры, вытащите из сумки еще n, иначе перейдите к 5.
- Напишите "- n" рядом с выражением, которое у вас уже есть
- Повторите с 2.
- Выполнить расчет
но вы этого не сделаете, потому что вы также можете сделать это так:
- Напишите 10 на бумаге
- Вытащите номер n из сумки
- Вычтите n из значения, которое вы имеете, сотрите значение и запишите новое значение вместо
- Повторите с 2.
Независимо от того, сколько чисел в сумке, вам нужно иметь только одно значение, написанное на бумаге. Исключение Tail Call (TCE) означает, что вместо построения большой структуры рекурсивных вызовов в стеке, вы можете выскочить и заменить накопленное значение в процессе работы. (То есть рекурсивно выраженное вычисление по существу выполняется итеративным способом.)
Как уже отмечалось, структура с произвольным доступом, такая как ArrayLike
позволяет foldRight
переставить в foldLeft
операции, и так получить право на ТВК. Приведенное выше описание действительно для случаев, когда это невозможно.