Анонимная рекурсия - Anonymous recursion

В информатике , анонимные рекурсии являются рекурсией , не явно вызывать функцию по имени. Это может быть сделано либо явно, используя функцию более высокого порядка - передавая функцию в качестве аргумента и вызывая ее, - либо неявно, с помощью функций отражения, которые позволяют получить доступ к определенным функциям в зависимости от текущего контекста, особенно «текущая функция "или иногда" вызывающая функция текущей функции ".

В практике программирования анонимная рекурсия особенно используется в JavaScript , который предоставляет средства отражения для ее поддержки. Однако в общей практике программирования это считается плохим стилем, и вместо него предлагается рекурсия с именованными функциями. Анонимная рекурсия посредством явной передачи функций в качестве аргументов возможна на любом языке, который поддерживает функции в качестве аргументов, хотя на практике это редко используется, поскольку оно длиннее и менее понятно, чем явное рекурсирование по имени.

В теоретической информатике важна анонимная рекурсия, поскольку она показывает, что можно реализовать рекурсию, не требуя именованных функций. Это особенно важно для лямбда-исчисления , которое имеет анонимные унарные функции, но может вычислять любую рекурсивную функцию. Эта анонимная рекурсия может быть произведена в общем с помощью комбинаторов с фиксированной точкой .

Использовать

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

Анонимная рекурсия в первую очередь состоит из вызова «текущей функции», что приводит к прямой рекурсии . Возможна анонимная косвенная рекурсия , например, путем вызова «вызывающего (предыдущая функция)» или, что реже, путем продвижения вверх по стеку вызовов , и это может быть связано для создания взаимной рекурсии . Само -ссылка «текущей функции» является функциональным эквивалентом ключевого слова « this » в объектно-ориентированном программировании , позволяя ссылаться на текущий контекст.

Анонимная рекурсия также может использоваться для именованных функций, а не для их вызова по имени, скажем, чтобы указать, что одна из них рекурсивно выполняет текущую функцию, или чтобы позволить переименовать функцию без необходимости изменять имя, в котором она вызывает себя. Однако с точки зрения стиля программирования этого обычно не происходит.

Альтернативы

Именованные функции

Обычная альтернатива - использовать именованные функции и именованную рекурсию. Для анонимной функции это можно сделать либо путем привязки имени к функции, как в выражениях именованных функций в JavaScript, либо путем присвоения функции переменной и последующего вызова этой переменной, как в операторах функции в JavaScript. Поскольку языки, которые разрешают анонимные функции, обычно позволяют назначать эти функции переменным (если не первоклассным функциям), многие языки не предоставляют способ ссылки на саму функцию и явно отвергают анонимную рекурсию; примеры включают Go .

Например, в JavaScript факториальную функцию можно определить через анонимную рекурсию как таковую:

[1, 2, 3, 4, 5].map(function(n) {
     return (!(n > 1)) ? 1 : arguments.callee(n-1) * n;
});

Переписанный для использования именованного выражения функции дает:

[1, 2, 3, 4, 5].map(function factorial(n) {
     return (!(n > 1)) ? 1 : factorial(n-1) * n;
});

Передача функций в качестве аргументов

Даже без механизмов для ссылки на текущую функцию или вызывающую функцию анонимная рекурсия возможна на языке, который допускает функции в качестве аргументов. Это делается путем добавления другого параметра к базовой рекурсивной функции и использования этого параметра в качестве функции для рекурсивного вызова. Это создает функцию более высокого порядка, а сама передача этой функции позволяет анонимную рекурсию внутри фактической рекурсивной функции. Это можно сделать чисто анонимно, применив комбинатор с фиксированной точкой к этой функции более высокого порядка. Это в основном представляет академический интерес, особенно для того, чтобы показать, что лямбда-исчисление имеет рекурсию, поскольку результирующее выражение значительно сложнее, чем исходная именованная рекурсивная функция. И наоборот, использование комбинаторов с фиксированной точкой можно в общем называть «анонимной рекурсией», так как это заметное их использование, хотя у них есть и другие приложения.

Это показано ниже с использованием Python. Во-первых, стандартная именованная рекурсия:

def fact(n):
    if n == 0:
        return 1
    return n * fact(n - 1)

Использование функции высшего порядка для анонимной рекурсии функции верхнего уровня по аргументу, но при этом для использования стандартной рекурсивной функции в качестве аргумента:

def fact0(n0):
    if n0 == 0:
        return 1
    return n0 * fact0(n0 - 1)
fact1 = lambda f, n1: 1 if n1 == 0 else n1 * f(n1 - 1)
fact = lambda n: fact1(fact0, n)

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

fact1 = lambda f, n1: 1 if n1 == 0 else n1 * f(f, n1 - 1)
fact = lambda n: fact1(fact1, n)

Вторую строку можно заменить универсальной функцией высшего порядка, называемой комбинатором :

F = lambda f: (lambda x: f(f, x))
fact1 = lambda f, n1: 1 if n1 == 0 else n1 * f(f, n1 - 1)
fact = F(fact1)

Написано анонимно:

(lambda f: (lambda x: f(f, x))) \
(lambda g, n1: 1 if n1 == 0 else n1 * g(g, n1 - 1))

В лямбда - исчислении , который использует только функции одной переменной, это может быть сделано через Y Combinator . Сначала сделайте функцию высшего порядка двух переменных функцией одной переменной, которая напрямую возвращает функцию, путем каррирования :

fact1 = lambda f: (lambda n1: 1 if n1 == 0 else n1 * f(f)(n1 - 1))
fact = fact1(fact1)

Здесь есть две операции «применения функции высшего порядка к самой себе»: f(f)в первой строке и fact1(fact1)во второй. Разложение второго двойного приложения в комбинатор дает:

C = lambda x: x(x)
fact1 = lambda f: (lambda n1: 1 if n1 == 0 else n1 * f(f)(n1 - 1))
fact = C(fact1)

Без учета другого двойного внесения дает:

C = lambda x: x(x)
D = lambda f: (lambda x: f(lambda v: x(x)(v)))
fact1 = lambda g: (lambda n1: 1 if n1 == 0 else n1 * g(n1 - 1))
fact = C(D(fact1))

Объединение двух комбинаторов в один дает комбинатор Y :

C = lambda x: x(x)
D = lambda f: (lambda x: f(lambda v: x(x)(v)))
Y = lambda y: C(D(y))
fact1 = lambda g: (lambda n1: 1 if n1 == 0 else n1 * g(n1 - 1))
fact = Y(fact1)

Расширение комбинатора Y дает:

Y = lambda f: (lambda x: f(lambda v: x(x)(v))) \
              (lambda x: f(lambda v: x(x)(v)))
fact1 = lambda g: (lambda n1: 1 if n1 == 0 else n1 * g(n1 - 1))
fact = Y(fact1)

Их объединение дает рекурсивное определение факториала в лямбда-исчислении (анонимные функции одной переменной):

(lambda f: (lambda x: f(lambda v: x(x)(v)))
           (lambda x: f(lambda v: x(x)(v)))) \
(lambda g: (lambda n1: 1 if n1 == 0 else n1 * g(n1 - 1)))

Примеры

APL

В APL текущий dfn доступен через . Это позволяет анонимную рекурсию, например, в этой реализации факториала:

   {0=⍵:1  × -1} 5
120
   {0=⍵:1  × -1}¨ 10    ⍝ applied to each element of 0 to 9
1 1 2 6 24 120 720 5040 40320 362880

JavaScript

В JavaScript текущая функция доступна через arguments.callee, а вызывающая функция - через arguments.caller. Они позволяют анонимную рекурсию, например, в этой реализации факториала:

[1, 2, 3, 4, 5].map(function(n) {
    return (!(n > 1)) ? 1 : arguments.callee(n - 1) * n;
});

Perl

Начиная с Perl 5.16, текущая подпрограмма доступна через __SUB__токен, который возвращает ссылку на текущую подпрограмму, или undefвне подпрограммы. Это позволяет анонимную рекурсию, например, в следующей реализации факториала:

#!/usr/bin/perl
use feature ":5.16";

print sub {
    my $x = shift;
    $x > 0
    ? $x * __SUB__->( $x - 1 )
    : 1;
}->(5), "\n";

р

В R текущую функцию можно вызвать с помощью Recall. Например,

sapply(0:5, function(n) {
  if (n == 0) return(1)
  n * Recall(n - 1)
})

Однако он не будет работать, если будет передан в качестве аргумента другой функции, например lapply, внутри определения анонимной функции. В этом случае sys.function(0)можно использовать. Например, приведенный ниже код рекурсивно возводит список в квадрат:

(function(x) {
  if (is.list(x)) {
    lapply(x, sys.function(0))
  } else {
    x^2
  }
})(list(list(1, 2, 3), list(4, 5)))

Ссылки