Анонімна рекурсія

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до навігації Перейти до пошуку

В інформатиці анонімна рекурсія є рекурсією, у якій не використовується виклик функції по імені. Це може бути зроблено або явно, використовуючи функцію більш високого порядку - передаючи функцію як аргумент і викликаючи її, - чи неявно, за допомогою можливостей рефлексії, які дозволяють отримати доступ до певних функцій поточного контексту[en], наприклад "поточну функцію", або "функцію що викликала поточну функцію"[1].

У теоретичній інформатиці важлива анонімна рекурсія, оскільки вона показує, що можна реалізувати рекурсію, не вимагаючи іменованих функцій. Це особливо важливо для лямбда-числення, яке має анонімні унарні функції, але може обчислювати будь-яку рекурсивную функцію. Ця анонімна рекурсія може бути проведена в загальному за допомогою комбінаторів з нерухомою точкою[en].

Використання[ред. | ред. код]

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

Анонімна рекурсія в основному складається з виклику «поточної функції», що призводить до прямої рекурсії. Можлива анонімна непряма рекурсія, наприклад, шляхом виклику функції, що викликає (попередня функція) або, рідше, шляхом просування ще вище по стеку викликів.

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

Альтернативи[ред. | ред. код]

Іменовані функції[ред. | ред. код]

Типова альтернатива - використовувати іменовані функції і іменовану рекурсію. Для анонімної функції це можна зробити або шляхом прив'язки імені до функції, як у виразах іменованих функцій в JavaScript, або шляхом присвоєння функції змінної і подальшого виклику цієї змінної, як в операторах функції в JavaScript. Оскільки мови, які дозволяють анонімні функції, зазвичай дозволяють присвоювати ці функції змінним, багато мов відкидають анонімну рекурсію; приклади включають Go[2].

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

[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;
});

Передача функцій як аргументів[ред. | ред. код]

Навіть без механізмів посилання на поточну функцію або функцію що її викликає, анонімна рекурсія можлива в мовах яка дозволяє функції як аргументи. Це робиться шляхом додавання іншого параметра до основної рекурсивної функції та використання цього параметра як функції для рекурсивного виклику. Це створює функцію вищого порядку, і передача цієї функції як аргумент самій собі дозволяє анонімну рекурсію в межах фактичної рекурсивної функції.

Це можна зробити повністю анонімно, застосувавши до цієї функції вищого порядку комбінатор з нерухомою точкою[en]. Це головним чином представляє академічний інтерес, особливо для того, щоб показати, що лямбда-числення має рекурсію, оскільки отриманий вираз значно складніший, ніж вихідна іменована рекурсивна функція. І навпаки, використання комбінаторів з нерухомою точкою загалом може називатися "анонімною рекурсією", оскільки це є найвідомішим їх використанням, хоча вони мають і інші застосування[4][5].

Продемонструємо це нижче, мовою Python. Спершу, стандартна іменована рекурсія:

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

Використовуючи функцію вищого порядку, що отримує сама себе як аргумент:

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)

Переписавши цей код анонімними функціями[6]:

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

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

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)

Поєднуючи ці результати, ми отримуємо рекурсивне визначення факторіалу в лямбда-численні (анонімні функції однієї змінної):[7]

(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[en] доступна через . Це дозволяє анонімну рекурсію, наприклад, у цій реалізації факторіалу:

   {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[1], тоді як функція виклику доступна через arguments.caller[8]. Вони дозволяють анонімну рекурсію, наприклад у цій реалізації факторіалу:

[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[ред. | ред. код]

У 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)))

Див. також[ред. | ред. код]

Зноски[ред. | ред. код]

  1. а б arguments.callee - JavaScript. MDN. 7 вересня 2023. Процитовано 11 лютого 2024.
  2. Issue 226: It's impossible to recurse a anonymous function in Go without workarounds. Архів оригіналу за 8 березня 2014. Процитовано 15 листопада 2020.
  3. answer by olliej, Oct 25 '08 to "Why was the arguments.callee.caller property deprecated in JavaScript?", StackOverflow
  4. * Trey Nash, Accelerated C# 2008, Apress, 2007, ISBN 1-59059-873-3, p. 462—463. Derived substantially from Wes Dyer's blog (see next item).
  5. The If Works Deriving the Y combinator [Архівовано 26 серпня 2009 у Wayback Machine.], January 10th, 2008
  6. Відповідь Hugo Walter-са на питання "Can a lambda function call itself recursively in Python?"
  7. Nux's answer to "Can a lambda function call itself recursively in Python?"
  8. Function.prototype.caller - JavaScript | MDN. MDN. 12 квітня 2023. Процитовано 11 лютого 2024.