javascript: funzione anonima ricorsiva?


120

Diciamo che ho una funzione ricorsiva di base:

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

Come potrei farlo se ho una funzione anonima come ...

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

Mi piacerebbe un modo per chiamare la funzione che ha chiamato questa funzione ... Ho visto script da qualche parte (non ricordo dove) che possono dirti il ​​nome di una funzione chiamata, ma non riesco a ricordare nessuno di quell'informazione adesso.


C'è un motivo per cui ne hai bisogno o sei solo curioso? Mi sembra che sarebbe più chiaro semplicemente dargli un nome ...
rfunduk

1
@thenduks: per lo stesso motivo per cui si dovrebbe utilizzare una funzione anonima. Solo che a volte è necessaria la ricorsione.
colpisci

5
È un peccato che arguments.calleeesista e questa funzione non fa nulla di utile. Stavo cercando su Y Combinator :P . Dannazione, quella roba non sarà mai utile ...
Kobi

1
Sì, come collegato a Kobi, usa un combinatore a punto fisso come Y per eseguire funzioni ricorsive anonime senza argomenti.
piroscafo 25

1
Vedi w3future.com/weblog/stories/2002/02/22/… per un esempio del combinatore Y in JS.
piroscafo 25

Risposte:


145

È possibile assegnare un nome alla funzione, anche quando si crea la funzione come valore e non come istruzione di "dichiarazione di funzione". In altre parole:

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

è una funzione ricorsiva di stacking. Detto questo, probabilmente non vorrai farlo in generale perché ci sono alcuni strani problemi con varie implementazioni di Javascript. ( nota : questo è un commento abbastanza vecchio; alcuni / molti / tutti i problemi descritti nel post del blog di Kangax potrebbero essere risolti nei browser più moderni.)

Quando dai un nome del genere, il nome non è visibile al di fuori della funzione (beh, non dovrebbe esserlo; questa è una delle stranezze). È come "letrec" in Lisp.

Quanto a arguments.callee , ciò non è consentito in modalità "rigorosa" e generalmente è considerato una cosa negativa, perché rende difficili alcune ottimizzazioni. È anche molto più lento di quanto ci si potrebbe aspettare.

modifica - Se vuoi avere l'effetto di una funzione "anonima" che può chiamare se stessa, puoi fare qualcosa del genere (supponendo che tu stia passando la funzione come callback o qualcosa del genere):

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

Ciò che fa è definire una funzione con un'istruzione di dichiarazione di funzione piacevole, sicura, non danneggiata in IE , creando una funzione locale il cui nome non inquinerà lo spazio dei nomi globale. La funzione wrapper (veramente anonima) restituisce solo quella funzione locale.


Possiamo evitare di inquinare lo spazio dei nomi globale in un altro modo con ES5 sctrict (non ho ancora letto in profondità ES5)?
Incognito

@pointy puoi per favore guardare questa ricerca. stackoverflow.com/questions/27473450/...
Gladson Robinson

Immagino non sia possibile utilizzare (() => { call_recursively_self_here() })()e chiamare se stesso in modo ricorsivo, giusto? Devo dargli un nome.
Qwerty

1
@ Qwerty, beh potresti fare qualcosa come l'ultimo esempio nella mia risposta. Associa la funzione freccia a una variabile locale in una funzione wrapper in modo che la funzione freccia possa fare riferimento a se stessa con il nome della variabile. Il wrapper restituirà quindi la variabile (che fa riferimento alla funzione freccia).
Pointy

1
@Pointy forse alcuni hacker troveranno l'applicazione;)
Kamil Kiełczewski

31

Le persone hanno parlato del combinatore Y nei commenti, ma nessuno lo ha scritto come risposta.

Il combinatore Y può essere definito in javascript come segue: (grazie a steamer25 per il link)

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

E quando vuoi passare la tua funzione anonima:

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

La cosa più importante da notare su questa soluzione è che non dovresti usarla.


16
"La cosa più importante da notare su questa soluzione è che non dovresti usarla." Perché?
nyuszika7h

7
Non sarà veloce. È brutto da usare (anche se concettualmente bello!). Eviti di dover dare alla tua funzione un tag o un nome di variabile (e non vedo perché sarebbe un problema), ma gli dai comunque un nome come parametro della funzione esterna passata a Y. Quindi non lo fai guadagnare qualcosa passando attraverso tutti questi guai.
Zem

Non dimenticare di menzionare che questa funzione non è sicura per lo stack. Il looping solo un paio di migliaia di volte provocherà un overflow dello stack.
Grazie

Ciao, vorrei proporre una modifica leggermente "più pulita" poiché .apply (null, argomenti) mi sembra brutto: var Y = function (gen) {return (function (f) {return f (f);} (function (f) {return gen (funzione (x) {return f (f) (x);});})); } O in modo equivalente ((function (x) {return y} è uguale a (x => y))) utilizzando la notazione a freccia (codice js valido): var Y = gen => (f => f (f)) (f = > gen (x => f (f) (x)))
myfirstRisposta

23

Combinatore U.

Passando una funzione a se stessa come argomento, una funzione può ricorrere utilizzando il suo parametro invece del suo nome! Quindi la funzione assegnata a Udovrebbe avere almeno un parametro che si legherà alla funzione (stessa).

Nell'esempio seguente, non abbiamo condizioni di uscita, quindi eseguiremo un ciclo indefinito fino a quando non si verificherà un overflow dello stack

const U = f => f (f) // call function f with itself as an argument

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

Possiamo fermare la ricorsione infinita usando una varietà di tecniche. Qui scriverò la nostra funzione anonima per restituire un'altra funzione anonima che attende un input; in questo caso, un certo numero. Quando viene fornito un numero, se è maggiore di 0, continueremo a ricorrere, altrimenti restituiremo 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

Ciò che non è immediatamente evidente qui è che la nostra funzione, quando applicata per la prima volta a se stessa utilizzando il Ucombinatore, restituisce una funzione in attesa del primo input. Se diamo un nome a questo, possiamo costruire efficacemente funzioni ricorsive usando lambda (funzioni anonime)

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

Solo che questa non è ricorsione diretta , una funzione che chiama se stessa usando il proprio nome. La nostra definizione di countDownnon si riferisce a se stessa all'interno del suo corpo ed è comunque possibile la ricorsione

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

Come rimuovere l'autoriferimento da una funzione esistente utilizzando il combinatore U.

Qui ti mostrerò come prendere una funzione ricorsiva che usa un riferimento a se stessa e cambiarla in una funzione che impiega il combinatore U al posto dell'autore riferimento

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

Ora usando il combinatore U per sostituire il riferimento interno a factorial

const U = f => f (f)

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

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

Il modello di sostituzione di base è questo. Prendi nota mentalmente, useremo una strategia simile nella prossima sezione

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

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

Combinatore Y.

correlati: i combinatori U e Y spiegati usando un'analogia speculare

Nella sezione precedente abbiamo visto come trasformare la ricorsione autoreferenziale in una funzione ricorsiva che non si basa su una funzione denominata utilizzando il combinatore U. C'è un po 'di fastidio anche se doversi ricordare di passare sempre la funzione a se stessa come primo argomento. Bene, il combinatore Y si basa sul combinatore U e rimuove quel pezzo noioso. Questa è una buona cosa perché rimuovere / ridurre la complessità è il motivo principale per cui creiamo funzioni

Innanzitutto, deriviamo il nostro combinatore Y molto personale

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

Ora vedremo come il suo utilizzo si confronta con il combinatore a U. Nota, per ricorrere, invece di U (f)possiamo semplicemente chiamaref ()

const U = f => f (f)

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

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

Ora mostrerò il countDownprogramma che usa Y: vedrai che i programmi sono quasi identici ma il combinatore Y mantiene le cose un po 'più pulite

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

E ora vedremo factorialanche noi

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

Come puoi vedere, fdiventa il meccanismo stesso della ricorsione. Per ricorrere, lo chiamiamo come una funzione ordinaria. Possiamo chiamarlo più volte con argomenti diversi e il risultato sarà comunque corretto. E poiché è un normale parametro di funzione, possiamo nominarlo come preferiamo, come di recurseguito -

const U = f => f (f)

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

const fibonacci = Y (recur => n =>
  n < 2 ? n : recur (n - 1) +  (n - 2))

console.log (fibonacci (10)) // 55


Combinatore U e Y con più di 1 parametro

Negli esempi precedenti, abbiamo visto come possiamo ripetere e passare un argomento per tenere traccia dello "stato" del nostro calcolo. Ma cosa succede se dobbiamo tenere traccia dello stato aggiuntivo?

Abbiamo potuto utilizzare i dati composto come un array o qualcosa del genere ...

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

Ma questo è negativo perché espone lo stato interno (contatori ae b). Sarebbe bello se potessimo semplicemente chiamare fibonacci (7)per ottenere la risposta che vogliamo.

Usando ciò che sappiamo sulle funzioni curry (sequenze di funzioni unarie (1 parametro)), possiamo raggiungere facilmente il nostro obiettivo senza dover modificare la nostra definizione di Y o fare affidamento su dati composti o funzionalità linguistiche avanzate.

Guarda la definizione di fibonaccisotto. Ci stiamo candidando immediatamente 0e 1che sono vincolati a ae brispettivamente. Ora fibonacci sta semplicemente aspettando che venga fornito l'ultimo argomento a cui sarà legato x. Quando ricorriamo, dobbiamo chiamare f (a) (b) (x)(non f (a,b,x)) perché la nostra funzione è in forma curata.

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


Questo tipo di pattern può essere utile per definire tutti i tipi di funzioni. Di seguito vedremo altre due funzioni definite utilizzando il Ycombinatore ( rangee reduce) e un derivato di 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 ]


È TUTTO ANONIMO OMG

Poiché qui stiamo lavorando con funzioni pure, possiamo sostituire qualsiasi funzione denominata per la sua definizione. Guarda cosa succede quando prendiamo Fibonacci e sostituiamo le funzioni con nome con le loro espressioni

/* 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 => h (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
  
console.log (result) // 13

Ed ecco fatto: fibonacci (7)calcolato in modo ricorsivo utilizzando nient'altro che funzioni anonime


14

Potrebbe essere più semplice utilizzare un "oggetto anonimo" invece:

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

Il tuo spazio globale è completamente incontaminato. È abbastanza semplice. E puoi facilmente sfruttare lo stato non globale dell'oggetto.

È inoltre possibile utilizzare i metodi degli oggetti ES6 per rendere la sintassi più concisa.

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

13

Non lo farei come funzione inline. Sta spingendo contro i confini del buon gusto e non ti dà davvero nulla.

Se proprio devi, c'è arguments.calleecome nella risposta di Fabrizio. Tuttavia, questo è generalmente considerato sconsigliabile e non è consentito nella "modalità rigorosa" di ECMAScript Fifth Edition. Sebbene ECMA 3 e la modalità non rigorosa non verranno eliminati, lavorare in modalità rigorosa promette più ottimizzazioni linguistiche possibili.

Si può anche usare una funzione inline denominata:

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

Tuttavia, è meglio evitare anche le espressioni di funzione inline denominate, poiché JScript di IE fa loro alcune cose cattive. Nell'esempio precedente fooinquina erroneamente l'ambito padre in IE e il genitore fooè un'istanza separata da quella foovista all'internofoo .

Qual è lo scopo di metterlo in una funzione anonima inline? Se vuoi solo evitare di inquinare l'ambito genitore, puoi ovviamente nascondere il tuo primo esempio all'interno di un'altra funzione auto-chiamata anonima (spazio dei nomi). Hai davvero bisogno di creare una nuova copia di nothingogni volta intorno alla ricorsione? Potresti stare meglio con uno spazio dei nomi contenente due semplici funzioni reciprocamente ricorsive.


Sono d'accordo, una funzione con nome è più adatta di arguments.callee non solo per la modalità rigorosa di ecmascript, ma anche per una questione di ottimizzazione perché ad ogni ricorsione ha bisogno di ottenere un riferimento al chiamato (e questo probabilmente potrebbe ridurre la velocità di esecuzione )

+1 per il poetico, "pushing against the boundaries of good taste"- (beh, e le buone informazioni).
Peter Ajtai

che dire di un semplice pre / postfisso se l'inquinamento è davvero un problema qui? Considerando che non è nell'ambito globale (anche se la funzione è top lvl, dovrebbe già avere una funzione anonima che avvolge tutto il suo codice) è davvero improbabile che un nome come recur_foosi scontrerà con una funzione nell'ambito genitore (o che sia malato -Usato) .
gblazex

Molto interessante - jsfiddle.net/hck2A - IE inquina il genitore in questo caso, come hai detto tu. Non l'ho mai capito.
Peter Ajtai

1
@Peter : kangax.github.com/nfe (specialmente 'JScript bugs') per più di quanto avresti mai voluto sapere su questo argomento. Finalmente è stato risolto in IE9 (ma solo in IE9 Standards Mode).
bobince

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

34
Spero che tutti coloro che votano per questa risposta (tecnicamente corretta) si rendano conto dei problemi con arguments.callee: è vietata in modalità rigorosa e in ES5.
Pointy

Votato, arguments.callee è deprecato in ES5
Jaime Rodriguez

Funziona in NodeJS. Non mi potrebbe importare di meno di ES5 fintanto che funziona in modo prevedibile su un ambiente fisso.
Angad

1
Questa è una bomba a orologeria. Non esiste una cosa chiamata ambiente "fisso", come suggerisce il commento sopra. Eseguiresti quasi sempre l'aggiornamento a causa di uno qualsiasi dei migliaia di motivi per farlo.
sampathsris

6

Potresti fare qualcosa come:

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

o nel tuo caso:

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

Si potrebbe fare con la dichiarazione recurprima con una vardichiarazione. Non so se ciò infrange le regole della domanda, ma come lo hai ora, senza la vardichiarazione riceverai un errore in ECMAScript 5 in modalità rigorosa.
Tim Down

Il mio commento iniziale includeva la varparola chiave, ma una volta testato questo codice, generava errori, poiché non è possibile dichiarare realmente una variabile all'interno di un blocco autoinvocante e il mio approccio si basa sulla dichiarazione automatica di una variabile non definita, e quindi @ Pointy's la soluzione è più corretta. Ma ho comunque votato per la risposta di Fabrizio Calderan però;)
ArtBIT

Sì, fare (var recur = function() {...})();non funzionerà poiché ora è un'istruzione piuttosto che un'espressione di assegnazione (che restituisce il valore assegnato). Stavo suggerendo var recur; (recur = function() {...})();invece.
Tim Down

3

Quando dichiari una funzione anonima come questa:

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

È considerata un'espressione di funzione e ha un nome opzionale (che puoi usare per chiamarla dall'interno di se stessa. Ma poiché è un'espressione di funzione (e non un'istruzione) rimane anonima (ma ha un nome che puoi chiamare). questa funzione può chiamare se stessa:

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

"rimane anonimo" - no non lo fa. Una funzione anonima non ha un nome. Capisco che foonon venga dichiarato nel contesto attuale, ma è più o meno irrilevante. Una funzione con un nome è ancora una funzione denominata, non anonima.
Grazie

3

Perché non passare la funzione alla funzione stessa?

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

3

In alcune situazioni devi fare affidamento su funzioni anonime. Dato è una mapfunzione ricorsiva :

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

Notare che mapnon deve modificare la struttura dell'array. Quindi l'accumulatore accnon deve essere esposto. Possiamo avvolgere mapin un'altra funzione, ad esempio:

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

  return next([])(xs);
}

Ma questa soluzione è piuttosto prolissa. Usiamo il Ucombinatore sottovalutato :

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

Conciso, non è vero? Uè semplicissimo ma ha lo svantaggio che la chiamata ricorsiva viene un po 'offuscata: sum(...)diventa h(h)(...)- tutto qui.


2

Non sono sicuro che la risposta sia ancora richiesta, ma questo può essere fatto anche utilizzando delegati creati utilizzando 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);

Questo non coinvolge funzioni o argomenti con nome.


1

Come ha scritto Bobince, semplicemente dai un nome alla tua funzione.

Ma immagino che tu voglia anche passare un valore iniziale e alla fine interrompere la tua funzione!

var initialValue = ...

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

esempio jsFiddle funzionante (usa dati + = dati per divertimento)



1
+1, questa è una risposta molto utile e dovresti ricevere più voti positivi, ma non è anonima.
Incognito

chiaramente non avete letto quello che ha scritto bobince: However named inline function expressions are also best avoided.. Ma anche l'OP perde il punto ... :)
gblazex

@ Galamb - L'ho letto. Non consentito in modalità rigorosa e in ES5 non equivale a inquinare un ambito padre e creare istanze aggiuntive.
Peter Ajtai

1

avevo bisogno (o meglio, volevo) una funzione anonima di una riga per camminare su un oggetto costruendo una stringa, e gestita in questo modo:

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

che produce una stringa come "Root: foo: bar: baz: ..."


1

Con ES2015 possiamo giocare un po 'con la sintassi e abusare di parametri e thunk predefiniti. Queste ultime sono solo funzioni senza argomenti:

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

Si noti che fè un parametro con la funzione anonima (x, y, n) => n === 0 ? x : f(y, x + y, n - 1)come valore predefinito. Quando fviene richiamato da applyTquesta chiamata deve avvenire senza argomenti, in modo che venga utilizzato il valore predefinito. Il valore predefinito è una funzione e quindi fè una funzione denominata, che può chiamare se stessa in modo ricorsivo.


0

Un'altra risposta che non coinvolge la funzione denominata o 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

bello: associa una funzione anonima a un parametro locale e quindi chiama la funzione tramite il parametro locale, ma passa anche la funzione a se stessa per la ricorsione.
englebart

0

Questa è una rielaborazione della risposta jforjs con nomi diversi e una voce leggermente modificata.

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

Non era necessario srotolare la prima ricorsione. La funzione che si riceve come riferimento richiama la melma primordiale dell'OOP.


0

Questa è una versione della risposta di @ zem con le funzioni delle frecce.

Puoi usare Uo ilY combinatore. Combinatore Y è il più semplice da usare.

U combinatore, con questo devi continuare a passare la funzione: const U = f => f(f) U(selfFn => arg => selfFn(selfFn)('to infinity and beyond'))

Y combinatore, con questo non devi continuare a passare la funzione: const Y = gen => U(f => gen((...args) => f(f)(...args))) Y(selfFn => arg => selfFn('to infinity and beyond'))


0

Ancora un'altra soluzione Y-combinator, utilizzando il collegamento codice rosetta (penso che qualcuno abbia precedentemente menzionato il collegamento da qualche parte su stackOverflow.

Le frecce sono per funzioni anonime più leggibili per me:

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

-1

Potrebbe non funzionare ovunque, ma puoi usarlo arguments.calleeper fare riferimento alla funzione corrente.

Quindi, fattoriale potrebbe essere fatto così:

var fac = function(x) { 
    if (x == 1) return x;
    else return x * arguments.callee(x-1);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.