JavaScript: рекурсивная анонимная функция?

Допустим, у меня есть базовая рекурсивная функция:

function recur(data) {
    data = data+1;
    var nothing = function() {
        recur(data);
    }
    nothing();
}

Как я могу сделать это, если у меня есть анонимная функция, такая как...

(function(data){
    data = data+1;
    var nothing = function() {
        //Something here that calls the function?
    }
    nothing();
})();

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

21 ответ

Решение

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

(function foo() { foo(); })();

является рекурсивной функцией выдувания стека Тем не менее, вы, возможно, не захотите делать это вообще, потому что есть некоторые странные проблемы с различными реализациями Javascript. (обратите внимание - это довольно старый комментарий; некоторые / многие / все проблемы, описанные в блоге Kangax, могут быть исправлены в более современных браузерах.)

Когда вы даете такое имя, имя не видно вне функции (ну, это не должно быть; это одна из странностей). Это как "letrec" в Лиспе.

Что касается arguments.calleeЭто запрещено в "строгом" режиме и, как правило, считается плохим, потому что это затрудняет некоторые оптимизации. Это также намного медленнее, чем можно было ожидать.

edit - Если вы хотите получить эффект "анонимной" функции, которая может вызывать себя, вы можете сделать что-то вроде этого (при условии, что вы передаете функцию в качестве обратного вызова или что-то в этом роде):

asyncThingWithCallback(params, (function() {
  function recursive() {
    if (timeToStop())
      return whatever();
    recursive(moreWork);
  }
  return recursive;
})());

То, что это делает, это определяет функцию с хорошим, безопасным, не нарушенным оператором объявления функции IE, создавая локальную функцию, имя которой не будет загрязнять глобальное пространство имен. Функция-обертка (действительно анонимная) просто возвращает эту локальную функцию.

Люди говорили о Y комбинаторе в комментариях, но никто не написал это как ответ.

Y-комбинатор может быть определен в javascript следующим образом: (спасибо steamer25 за ссылку)

var Y = function (gen) {
  return (function(f) {
    return f(f);
  }(function(f) {
    return gen(function() {
      return f(f).apply(null, arguments);
    });
  }));
}

И когда вы хотите передать свою анонимную функцию:

(Y(function(recur) {
  return function(data) {
    data = data+1;
    var nothing = function() {
      recur(data);
    }
    nothing();
  }
})());

Самое важное, что следует отметить в этом решении, это то, что вы не должны его использовать.

U комбинатор

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

В приведенном ниже примере у нас нет условия выхода, поэтому мы будем бесконечно зацикливаться, пока не произойдет переполнение стека

const U = f => f (f)

U (f => (console.log ('stack overflow imminent!'), U (f)))

Мы можем остановить бесконечную рекурсию, используя различные методы. Здесь я напишу нашу анонимную функцию, чтобы она возвращала другую анонимную функцию, ожидающую ввода; в этом случае какое-то число. Если указано число, если оно больше 0, мы продолжим повторение, в противном случае вернем 0.

const log = x => (console.log (x), x)

const U = f => f (f)

// when our function is applied to itself, we get the inner function back
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
// returns: (x => x > 0 ? U (f) (log (x - 1)) : 0)
// where f is a reference to our outer function

// watch when we apply an argument to this function, eg 5
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0) (5)
// 4 3 2 1 0

Что тут не очевидно, так это то, что наша функция при первом применении к себе, используя U комбинатор, он возвращает функцию, ожидающую первого ввода. Если бы мы дали имя этому, можно эффективно создавать рекурсивные функции, используя лямбда-выражения (анонимные функции)

const log = x => (console.log (x), x)

const U = f => f (f)

const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)

countDown (5)
// 4 3 2 1 0

countDown (3)
// 2 1 0

Только это не прямая рекурсия - функция, которая вызывает себя, используя свое собственное имя. Наше определение countDown не ссылается на себя внутри своего тела, но возможна рекурсия

// direct recursion references itself by name
const loop = (params) => {
  if (condition)
    return someValue
  else
    // loop references itself to recur...
    return loop (adjustedParams)
}

// U combinator does not need a named reference
// no reference to `countDown` inside countDown's definition
const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)

Как удалить самоссылку из существующей функции с помощью U комбинатора

Здесь я покажу вам, как взять рекурсивную функцию, которая использует ссылку на себя, и заменить ее на функцию, которая использует U-комбинатор вместо собственной ссылки

const factorial = x =>
  x === 0 ? 1 : x * factorial (x - 1)
  
console.log (factorial (5)) // 120

Теперь с помощью комбинатора U замените внутреннюю ссылку на factorial

const U = f => f (f)

const factorial = U (f => x =>
  x === 0 ? 1 : x * U (f) (x - 1))

console.log (factorial (5)) // 120

Основной шаблон замены заключается в следующем. Запомните, мы будем использовать аналогичную стратегию в следующем разделе.

// self reference recursion
const foo =         x => ...   foo (nextX) ...

// remove self reference with U combinator
const foo = U (f => x => ... U (f) (nextX) ...)

Y комбинатор

связанные: U и Y комбинаторы объяснили, используя зеркальную аналогию

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

Во-первых, давайте выведем наш собственный Y-комбинатор

// standard definition
const Y = f => f (Y (f))

// prevent immediate infinite recursion in applicative order language (JS)
const Y = f => f (x => Y (f) (x))

// remove reference to self using U combinator
const Y = U (h => f => f (x => U (h) (f) (x)))

Теперь мы увидим, как его использование сравнивается с U-комбинатором. Обратите внимание, чтобы повториться, а не U (f) мы можем просто позвонить f ()

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

Y (f => (console.log ('stack overflow imminent!'),  f ()))

Теперь я продемонстрирую countDown программа с использованием Y - вы увидите, что программы почти идентичны, но Y-комбинатор делает вещи немного чище

const log = x => (console.log (x), x)

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const countDown = Y (f => x => x > 0 ? f (log (x - 1)) : 0)

countDown (5)
// 4 3 2 1 0

countDown (3)
// 2 1 0

А теперь посмотрим factorial также

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const factorial = Y (f => x =>
  x === 0 ? 1 :  x * f (x - 1))

console.log (factorial (5)) // 120


U и Y комбинатор с более чем 1 параметром

В приведенных выше примерах мы увидели, как мы можем зациклить и передать аргумент для отслеживания "состояния" наших вычислений. Но что, если нам нужно отслеживать дополнительное состояние?

Мы могли бы использовать составные данные, такие как массив или что-то...

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const fibonacci = Y (f => ([a, b, x]) =>
  x === 0 ? a : f ([b, a + b, x - 1]))

// starting with 0 and 1, generate the 7th number in the sequence
console.log (fibonacci ([0, 1, 7])) 
// 0 1 1 2 3 5 8 13

Но это плохо, потому что это разоблачает внутреннее состояние (счетчики a а также b). Было бы хорошо, если бы мы могли просто позвонить fibonacci (7) чтобы получить ответ, который мы хотим.

Используя то, что мы знаем о карри функциях (последовательности унарных (1-параметрических) функций), мы можем легко достичь нашей цели без необходимости изменять наше определение Y или полагаться на сложные данные или расширенные функции языка.

Посмотрите на определение fibonacci близко внизу. Мы немедленно подаем заявку 0 а также 1 которые связаны с a а также b соответственно. Теперь Фибоначчи просто ожидает последнего аргумента, который будет связан с x, Когда мы возвращаемся, мы должны позвонить f (a) (b) (x) (не f (a,b,x)) потому что наша функция в форме карри.

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const fibonacci = Y (f => a => b => x =>
  x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)

console.log (fibonacci (7)) 
// 0 1 1 2 3 5 8 13


Этот тип шаблона может быть полезен для определения всех видов функций. Ниже мы увидим еще две функции, определенные с помощью Y комбинатор (range а также reduce) и производная от reduce, map,

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const range = Y (f => acc => min => max =>
  min > max ? acc : f ([...acc, min]) (min + 1) (max)) ([])

const reduce = Y (f => g => y => ([x,...xs]) =>
  x === undefined ? y : f (g) (g (y) (x)) (xs))
  
const map = f =>
  reduce (ys => x => [...ys, f (x)]) ([])
  
const add = x => y => x + y

const sq = x => x * x

console.log (range (-2) (2))
// [ -2, -1, 0, 1, 2 ]

console.log (reduce (add) (0) ([1,2,3,4]))
// 10

console.log (map (sq) ([1,2,3,4]))
// [ 1, 4, 9, 16 ]


Это все анонимный OMG

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

/* const U = f => f (f)
 *
 * const Y = U (h => f => f (x => U (h) (f) (x)))
 *
 * const fibonacci = Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)
 *
 */

/*
 * given fibonacci (7)
 *
 * replace fibonacci with its definition
 * Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
 *
 * replace Y with its definition
 * U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
//
 * replace U with its definition
 * (f => f (f)) U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
 */

let result =
  (f => f (f)) (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
  
console.log (result) // 13

И у вас это есть - fibonacci (7) вычисляется рекурсивно, используя только анонимные функции

Вместо этого может быть проще использовать "анонимный объект":

({
  do: function() {
    console.log("don't run this ...");
    this.do();
  }
}).do();

Ваше глобальное пространство полностью незагрязнено. Это довольно просто. И вы можете легко воспользоваться неглобальным состоянием объекта.

Я бы не стал делать это как встроенную функцию. Это выходит за границы хорошего вкуса и на самом деле ничего вам не дает.

Если вы действительно должны, есть arguments.callee как в ответе Фабрицио. Однако это обычно считается нецелесообразным и не допускается в "строгом режиме" ECMAScript пятого издания. Хотя ECMA 3 и не строгий режим не исчезают, работа в строгом режиме обещает больше возможных языковых оптимизаций.

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

(function foo(data){
    data++;
    var nothing = function() {
        foo(data);
    }
    nothing();
})();

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

Какова цель помещения этого во встроенную анонимную функцию? Если вы просто хотите избежать загрязнения родительской области видимости, вы, конечно, можете спрятать свой первый пример внутри другой функции, вызывающей себя анонимно (пространство имен). Вам действительно нужно создать новую копию nothing каждый раз вокруг рекурсии? Возможно, вам лучше использовать пространство имен, содержащее две простые взаимно-рекурсивные функции.

(function(data){
    var recursive = arguments.callee;
    data = data+1;
    var nothing = function() {
        recursive(data)
    }
    nothing();
})();

Вы могли бы сделать что-то вроде:

(foo = function() { foo(); })()

или в вашем случае:

(recur = function(data){
    data = data+1;
    var nothing = function() {
        if (data > 100) return; // put recursion limit
        recur(data);
    }
    nothing();
})(/* put data init value here */ 0);

Когда вы объявляете анонимную функцию следующим образом:

(function () {
    // Pass
}());

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

(function foo () {
    foo();
}());
foo //-> undefined

В определенных ситуациях вы должны полагаться на анонимные функции. Данный рекурсив map функция:

const map = f => acc => ([head, ...tail]) => head === undefined 
 ? acc
 : map (f) ([...acc, f(head)]) (tail);

const sqr = x => x * x;
const xs = [1,2,3,4,5];

console.log(map(sqr) ([0]) (xs)); // [0] modifies the structure of the array

Обратите внимание, что map не должен изменять структуру массива. Итак, аккумулятор acc не нужно быть разоблаченным. Мы можем завернуть map в другую функцию, например:

const map = f => xs => {
  let next = acc => ([head, ...tail]) => head === undefined
   ? acc
   : map ([...acc, f(head)]) (tail);

  return next([])(xs);
}

Но это решение довольно многословно. Давайте использовать недооцененный U комбинатор:

const U = f => f(f);

const map = f => U(h => acc => ([head, ...tail]) => head === undefined 
 ? acc
 : h(h)([...acc, f(head)])(tail))([]);

const sqr = x => x * x;
const xs = [1,2,3,4,5];

console.log(map(sqr) (xs));

Кратко, не так ли? U очень прост, но имеет тот недостаток, что рекурсивный вызов немного запутывается: sum(...) становится h(h)(...) - это все.

Почему бы не передать функцию самой функции?

    var functionCaller = function(thisCaller, data) {
        data = data + 1;
        var nothing = function() {
            thisCaller(thisCaller, data);
        };
        nothing();
    };
    functionCaller(functionCaller, data);

С ES2015 мы можем немного поиграться с синтаксисом и злоупотреблять стандартными параметрами и блоками. Последние являются просто функциями без каких-либо аргументов:

const applyT = thunk => thunk();

const fib = n => applyT(
  (f = (x, y, n) => n === 0 ? x : f(y, x + y, n - 1)) => f(0, 1, n)
);

console.log(fib(10)); // 55

// Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55...

Обратите внимание, что f это параметр с анонимной функцией (x, y, n) => n === 0 ? x : f(y, x + y, n - 1) в качестве значения по умолчанию. когда f вызывается applyT этот вызов должен выполняться без аргументов, поэтому используется значение по умолчанию. Значением по умолчанию является функция и, следовательно, f является именованной функцией, которая может вызывать себя рекурсивно.

Я не уверен, что ответ по-прежнему требуется, но это также можно сделать с помощью делегатов, созданных с помощью function.bind:

    var x = ((function () {
        return this.bind(this, arguments[0])();
    }).bind(function (n) {
        if (n != 1) {
            return n * this.bind(this, (n - 1))();
        }
        else {
            return 1;
        }
    }))(5);

    console.log(x);

Это не касается именованных функций или arguments.callee.

Как писал Бобинс, просто назовите свою функцию.

Но я предполагаю, что вы также хотите передать начальное значение и остановить свою функцию в конце концов!

var initialValue = ...

(function recurse(data){
    data++;
    var nothing = function() {
        recurse(data);
    }
    if ( ... stop condition ... )
        { ... display result, etc. ... }
    else
        nothing();
}(initialValue));

рабочий пример jsFiddle (использует данные += данные для развлечения)


Мне нужна была (или, скорее, я хотела) анонимная функция, состоящая из одной строки, чтобы пройти по объекту, создающему строку, и обрабатывать ее следующим образом:

var cmTitle = 'Root' + (function cmCatRecurse(cmCat){return (cmCat == root) ? '' : cmCatRecurse(cmCat.parent) + ' : ' + cmCat.getDisplayName();})(cmCurrentCat);

которая производит строку типа 'Root: foo: bar: baz: ...'

Другой ответ, который не включает именованную функцию или arguments.callee

var sum = (function(foo,n){
  return n + foo(foo,n-1);
})(function(foo,n){
     if(n>1){
         return n + foo(foo,n-1)
     }else{
         return n;
     }
},5); //function takes two argument one is function and another is 5

console.log(sum) //output : 15

используяarguments.callee(). Для получения более подробной информации посетите этот URL-адрес: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#scope_and_the_function_stack.

      (function(data){
    data = data+1;
    var nothing = function() {
       arguments.callee() // this calls the function itself
    }
    nothing();
})();

Это доработка ответа jforjs с разными именами и слегка измененной записью.

// function takes two argument: first is recursive function and second is input
var sum = (function(capturedRecurser,n){
  return capturedRecurser(capturedRecurser, n);
})(function(thisFunction,n){
     if(n>1){
         return n + thisFunction(thisFunction,n-1)
     }else{
         return n;
     }
},5); 

console.log(sum) //output : 15

Не было необходимости развертывать первую рекурсию. Функция, принимающая себя в качестве эталона, возвращает нас к изначальному источнику ООП.

Это версия ответа @zem с функциями стрелок.

Вы можете использовать U или Y комбинатор. Y комбинатор является самым простым в использовании.

U комбинатор, с этим вы должны продолжать передавать функцию: const U = f => f(f) U(selfFn => arg => selfFn(selfFn)('to infinity and beyond'))

Y комбинатор, с этим вам не нужно постоянно передавать функцию: const Y = gen => U(f => gen((...args) => f(f)(...args))) Y(selfFn => arg => selfFn('to infinity and beyond'))

Еще одно решение Y-комбинатора, использующее ссылку на код розетты (я думаю, что кто-то ранее упоминал ссылку где-то в stackOverflow.

Стрелки для анонимных функций более читабельны для меня:

var Y = f => (x => x(x))(y => f(x => y(y)(x)));

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

      (f => f(f))(f => {
    data = data+1;
    var nothing = function() {
        f();
    }
    nothing(f);
});

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

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

Итак, факториал можно сделать так:

var fac = function(x) { 
    if (x == 1) return x;
    else return x * arguments.callee(x-1);
}
Другие вопросы по тегам