Почему 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 являются хвостовой рекурсией, а их правые аналоги - нет?

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

Как вы знаете, когда использовать сложение влево и когда использовать сложение вправо?

Последствия фолд против фолд (или фолдл)

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

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 Если вы вынуждены сделать это так:

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

в foldLeft случай, вы можете сделать это так:

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

но вы этого не сделаете, потому что вы также можете сделать это так:

  1. Напишите 10 на бумаге
  2. Вытащите номер n из сумки
  3. Вычтите n из значения, которое вы имеете, сотрите значение и запишите новое значение вместо
  4. Повторите с 2.

Независимо от того, сколько чисел в сумке, вам нужно иметь только одно значение, написанное на бумаге. Исключение Tail Call (TCE) означает, что вместо построения большой структуры рекурсивных вызовов в стеке, вы можете выскочить и заменить накопленное значение в процессе работы. (То есть рекурсивно выраженное вычисление по существу выполняется итеративным способом.)

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

Другие вопросы по тегам