web-dev-qa-db-fra.com

javascript: fonction anonyme récursive?

Disons que j'ai une fonction récursive de base:

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

Comment pourrais-je faire cela si j'ai une fonction anonyme telle que ...

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

Je voudrais un moyen d'appeler la fonction qui a appelé cette fonction ... J'ai vu des scripts quelque part (je ne me souviens plus où) pouvant vous dire le nom d'une fonction appelée cette information en ce moment.

106
Incognito

Vous pouvez attribuer un nom à la fonction, même lorsque vous la créez en tant que valeur et non en tant qu'instruction "déclaration de fonction". En d'autres termes:

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

est une fonction récursive de soufflage de pile. Maintenant, cela dit, vous  probablement pas peut ne pas vouloir faire ceci en général car il y a quelques problèmes étranges avec diverses implémentations de Javascript. (note _ - c'est un commentaire assez ancien; certains/plusieurs/tous les problèmes décrits dans l'article de blog de Kangax peuvent être résolus dans des navigateurs plus modernes.)

Lorsque vous donnez un nom comme celui-là, le nom n'est pas visible en dehors de la fonction (enfin, ce n'est pas censé l'être; c'est l'une des bizarreries). C'est comme "letrec" dans LISP.

Quant à arguments.callee, il est interdit en mode "strict" et est généralement considéré comme une mauvaise chose, car il rend certaines optimisations difficiles. C'est aussi beaucoup plus lent qu'on pourrait s'y attendre.

edit - Si vous voulez avoir l'effet d'une fonction "anonyme" qui peut s'appeler, vous pouvez faire quelque chose comme ceci (en supposant que vous transmettiez la fonction en tant que rappel ou quelque chose comme ça):

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

Cela permet de définir une fonction avec une instruction function declaration de Nice, sûre et non cassée dans IE, créant une fonction locale dont le nom ne pollue pas l'espace de noms global. La fonction wrapper (vraiment anonyme) ne fait que renvoyer cette fonction locale.

131
Pointy

Les gens ont parlé du Y Combinator dans des commentaires, mais personne ne l’a écrit pour y répondre.

Le Y Combinator peut être défini en javascript comme suit: (merci à steamer25 pour le lien)

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

Et quand vous voulez passer votre fonction anonyme:

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

La chose la plus importante à noter à propos de cette solution est que vous ne devriez pas l’utiliser.

30
zem

Je ne ferais pas cela comme une fonction inline. Cela repousse les limites du bon goût et ne vous procure rien.

Si vous devez vraiment, il y a arguments.callee comme dans la réponse de Fabrizio. Cependant, cela est généralement considéré comme déconseillé et n’est pas autorisé dans le ‘mode strict’ ​​d’ECMAScript Fifth Edition. Bien que l'ECMA 3 et le mode non strict ne disparaissent pas, le travail en mode strict promet davantage d'optimisations linguistiques.

On peut aussi utiliser une fonction inline nommée:

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

Toutefois, il vaut mieux éviter les expressions de fonction en-ligne nommées, car JScript d'IE lui inflige de mauvaises choses. Dans l'exemple ci-dessus, foo pollue de manière incorrecte la portée parent dans IE et le parent foo est une instance distincte de la foo vue à l'intérieur de foo.

Quel est le but de mettre cela dans une fonction anonyme en ligne? Si vous voulez simplement éviter de polluer la portée parent, vous pouvez bien sûr cacher votre premier exemple dans une autre fonction (nom de l'espace) anonyme. Avez-vous vraiment besoin de créer une nouvelle copie de nothing à chaque fois autour de la récursivité? Vous pourriez être mieux avec un espace de noms contenant deux fonctions simples mutuellement récursives.

13
bobince

Il peut être plus simple d’utiliser un "objet anonyme" à la place:

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

Votre espace global est complètement non pollué. C'est assez simple. Et vous pouvez facilement tirer parti de l'état non global de l'objet.

12
svidgen

U combinator

Le combinateur en U prend une fonction et l'applique à lui-même. Donc, la fonction que vous lui donnez devrait au moins avoir un paramètre qui se liera à la fonction (elle-même)

Dans l'exemple ci-dessous, nous n'avons pas de condition de sortie, nous allons donc boucler indéfiniment jusqu'à ce qu'un débordement de pile se produise

const U = f => f (f)

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

Nous pouvons arrêter la récursion infinie en utilisant une variété de techniques. Ici, je vais écrire notre fonction anonyme pour renvoyer une autre fonction anonyme qui attend une entrée; dans ce cas, un certain nombre. Lorsqu'un nombre est fourni, s'il est supérieur à 0, nous allons continuer à le répéter, sinon nous retournons 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

Ce qui n'est pas immédiatement apparent ici, c'est que notre fonction, lorsqu'elle s'applique pour la première fois à l'aide du combinateur U, renvoie une fonction en attente de la première entrée. Si nous avons donné un nom à cela, nous pouvons effectivement construire des fonctions récursives en utilisant lambdas (fonctions anonymes)

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

Seulement ceci n'est pas direct _ récursivité - une fonction qui s'appelle elle-même en utilisant son propre nom. Notre définition de countDown ne fait pas référence à elle-même à l'intérieur de son corps et la récursion est toujours possible

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

Comment supprimer l'auto-référence d'une fonction existante en utilisant U combinator

Ici, je vais vous montrer comment prendre une fonction récursive qui utilise une référence à elle-même et la changer en une fonction qui utilise le combinateur en U à la place de la référence de soi.

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

Maintenant, utilisez le combinateur U pour remplacer la référence interne à factorial

const U = f => f (f)

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

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

Le schéma de remplacement de base est le suivant. Faites une note mentale, nous allons utiliser une stratégie similaire dans la section suivante

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

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

Y Combinator

liées: les combinateurs U et Y expliqués à l'aide d'une analogie miroir

Dans la section précédente, nous avons vu comment transformer la récursivité auto-référencée en une fonction récursive qui ne repose pas sur une fonction nommée utilisant le combinateur en U. Il est un peu gênant de devoir se rappeler de toujours passer la fonction à elle-même en tant que premier argument. Eh bien, le combinateur Y repose sur le combinateur U et supprime ce bit fastidieux. C’est une bonne chose car éliminer/réduire la complexité est la raison principale pour laquelle nous créons des fonctions.

D'abord, dérivons notre propre combinateur en 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)))

Nous allons maintenant voir comment son utilisation se compare au combinateur en U. Remarquez, pour revenir, au lieu de U (f), nous pouvons simplement appeler 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 ()))

Maintenant, je vais démontrer le programme countDown en utilisant Y - vous verrez que les programmes sont presque identiques mais que le Y Combinator garde les choses un peu plus propres

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

Et maintenant nous verrons aussi 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 et Y Combinator avec plus d'un paramètre

Dans les exemples ci-dessus, nous avons vu comment nous pouvons boucler et passer un argument pour garder une trace de "l'état" de notre calcul. Mais que se passe-t-il si nous devons garder trace de l'état supplémentaire?

Nous pourrions utiliser des données composées comme un tableau ou autre chose ...

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

Mais c’est mauvais parce que cela expose l’état interne (compteurs a et b). Ce serait bien si nous pouvions simplement appeler fibonacci (7) pour obtenir la réponse souhaitée.

En utilisant ce que nous savons des fonctions curryed (séquences de fonctions unaires (1-paramètre)), nous pouvons facilement atteindre notre objectif sans avoir à modifier notre définition de Y ou à nous appuyer sur des données composées ou des fonctionnalités de langage avancées.

Regardez la définition de fibonacci de près ci-dessous. Nous appliquons immédiatement 0 et 1 qui sont liés à a et b respectivement. Maintenant, fibonacci attend simplement le dernier argument à fournir qui sera lié à x. Lorsque nous recurse, nous devons appeler f (a) (b) (x) (pas f (a,b,x)) car notre fonction est sous forme curry.

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


Ce type de modèle peut être utile pour définir toutes sortes de fonctions. Ci-dessous, nous verrons deux autres fonctions définies à l'aide du combinateur Y (range et reduce) et d'un dérivé de 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 ]


C'EST TOUT ANONYME OMG

Parce que nous travaillons avec des fonctions pures ici, nous pouvons substituer n'importe quelle fonction nommée à sa définition. Regardez ce qui se passe lorsque nous prenons fibonacci et remplaçons les fonctions nommées par leurs expressions

/* 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

Et voilà - fibonacci (7) calculé récursivement en utilisant uniquement des fonctions anonymes

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

Vous pourriez faire quelque chose comme:

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

ou dans votre cas:

(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);
6
ArtBIT

Pourquoi ne pas passer la fonction à la fonction elle-même?

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

Lorsque vous déclarez une fonction anonyme comme celle-ci:

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

Il est considéré comme une expression de fonction et possède un nom facultatif (que vous pouvez utiliser pour l'appeler de l'intérieur). Mais comme c'est une expression de fonction (et non une instruction), il reste anonyme (mais porte un nom que vous pouvez appeler). cette fonction peut s'appeler elle-même:

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

Dans certaines situations, vous devez utiliser des fonctions anonymes. Donné est une fonction map récursive:

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

Veuillez noter que map ne doit pas modifier la structure du tableau. Donc, l'accumulateur acc n'a pas besoin d'être exposé. Nous pouvons envelopper map dans une autre fonction, par exemple:

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

  return next([])(xs);
}

Mais cette solution est assez prolixe. Utilisons le combinateur U sous-estimé:

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

Concis, n'est ce pas? U est extrêmement simple mais présente le désavantage que l'appel récursif soit un peu obscurci: sum(...) devient h(h)(...) - c'est tout.

3
user6445533

Je ne sais pas si la réponse est toujours requise, mais cela peut également être fait avec des délégués créés avec 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);

Cela n'implique pas de fonctions nommées ni d'arguments.callee.

2
Nitij

Comme l'a écrit Bobince, nommez simplement votre fonction.

Mais je suppose que vous souhaitez également transmettre une valeur initiale et arrêter votre fonction par la suite!

var initialValue = ...

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

Exemple de travail jsFiddle (utilise data + = data for fun)


1
Peter Ajtai

j'avais besoin (ou plutôt, je voulais) d'une fonction anonyme one-liner pour remonter un objet constituant une chaîne, et je l'ai traité comme suit:

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

qui produit une chaîne comme 'Root: foo: bar: baz: ...'

1
radio_babylon

Avec ES2015, nous pouvons jouer un peu avec la syntaxe et abuser des paramètres par défaut et des thunks. Ces derniers ne sont que des fonctions sans aucun argument:

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...

Veuillez noter que f est un paramètre avec la fonction anonyme (x, y, n) => n === 0 ? x : f(y, x + y, n - 1) comme valeur par défaut. Lorsque f est appelé par applyT, cet appel doit avoir lieu sans argument, de sorte que la valeur par défaut est utilisée. La valeur par défaut est une fonction et par conséquent f est une fonction nommée, qui peut s’appeler de manière récursive.

1
user6445533

Une autre réponse qui n'implique pas de fonction nommée ni d'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
0
jforjs

Ceci est une retouche de la réponse de jforjs avec des noms différents et une entrée légèrement modifiée. 

// 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

Il n'était pas nécessaire de dérouler la première récursivité. La fonction qui se reçoit comme référence renvoie au vase primordial de la POO.

0
englebart

Encore une autre solution Y-combinator, utilisant rosetta-code link (je pense que quelqu'un a déjà mentionné le lien quelque part sur stackOverflow. 

Les flèches sont pour les fonctions anonymes plus lisibles pour moi:

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

Ceci est une version de la réponse de @ zem avec des fonctions de flèche.

Vous pouvez utiliser le combinateur U ou Y. Y Combinator étant le plus simple à utiliser.

U combinator, avec ceci vous devez continuer à transmettre la fonction: const U = f => f(f) U(selfFn => arg => selfFn(selfFn)('to infinity and beyond'))

Y combinator, avec ceci vous n'avez pas à continuer à passer la fonction: const Y = gen => U(f => gen((...args) => f(f)(...args))) Y(selfFn => arg => selfFn('to infinity and beyond'))

0
Ricardo Freitas