javascript:递归匿名函数?

时间:2010-10-07 16:34:33

标签: javascript recursion scope anonymous-function

假设我有一个基本的递归函数:

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

我想要一种方法来调用调用这个函数的函数...我已经看到某个地方的脚本(我记不清哪里)可以告诉你一个叫做函数的名字,但我不能现在回忆起任何这些信息。

20 个答案:

答案 0 :(得分:132)

可以为函数命名,即使您将函数创建为值而不是“函数声明”语句。换句话说:

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

是一个堆栈递归函数。现在,这就是说,你probably don't may not want to do this一般因为Javascript的各种实现存在一些奇怪的问题。 ( note - 这是一个相当古老的评论; Kangax博客文章中描述的一些/很多/所有问题可能会在更现代的浏览器中修复。)

当你给出这样的名字时,这个名字在功能之外是不可见的(好吧,它不应该是;这是奇怪之一)。这就像Lisp中的“letrec”。

对于arguments.callee,这在“严格”模式下是不允许的,并且通常被认为是一件坏事,因为它会使一些优化变得困难。它也比人们预期的要慢得多。

编辑 - 如果你想拥有一个可以调用自身的“匿名”函数的效果,你可以做这样的事情(假设你把这个函数作为一个回调或类似的东西传递即):

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

它的作用是定义一个具有漂亮,安全,未破坏的IE函数声明语句的函数,创建一个名称不会污染全局名称空间的本地函数。包装器(真正的匿名)函数只返回本地函数。

答案 1 :(得分:30)

人们在评论中谈到了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();
  }
})());

关于这个解决方案最重要的一点是你不应该使用它。

答案 2 :(得分:14)

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组合器应用于自身时,它返回一个等待第一个输入的函数。如果我们为此命名,可以使用lambdas(匿名函数)

有效地构造递归函数



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组合

  

相关:the U and Y combinators explained using a mirror analogy

在上一节中,我们看到了如何将自引用递归转换为不依赖于使用U组合子的命名函数的递归函数。因为必须记住总是将函数作为第一个参数传递给自己,所以有点烦恼。好吧,Y-combinator建立在U-co​​mbinator的基础上,并删除了那个乏味的位。这是一件好事,因为删除/降低复杂性是我们制作函数的主要原因

首先,让我们推导出我们自己的Y-combinator

// 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-combinator相比如何。请注意,要重复,我们只需拨打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




但这很糟糕,因为它暴露了内部状态(计数器ab)。如果我们可以致电fibonacci (7)来获得我们想要的答案,那就太好了。

使用我们对curried函数(一元(1-paramter)函数序列)的了解,我们可以轻松实现我们的目标,而无需修改Y的定义或依赖复合数据或高级语言功能。

请仔细阅读以下fibonacci的定义。我们会立即应用分别与01绑定的ab。现在斐波那契只是在等待提供的最后一个参数,它将绑定到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组合子(rangereduce)以及reducemap的衍生物定义的另外两个函数。



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 ]




IT全部匿名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)递归计算只使用匿名函数

答案 3 :(得分:13)

我不会这样做作为内联函数。它正在推动良好品味的界限,并没有真正让你得到任何东西。

如果你真的必须,那就像Fabrizio的回答一样arguments.callee。然而,这通常被认为是不可取的,并且在ECMAScript第五版的“严格模式”中是不允许的。虽然ECMA 3和非严格模式不会消失,但在严格模式下工作可以提供更多可能的语言优化。

还可以使用命名内联函数:

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

然而,最好避免使用命名的内联函数表达式,因为IE的JScript会给它们带来一些不好的东西。在上面的示例中,foo错误地污染了IE中的父作用域,而父foofoofoo所见的单独实例。

将其置于内联匿名函数中的目的是什么?如果您只是想避免污染父作用域,您当然可以将您的第一个示例隐藏在另一个自调用匿名函数(命名空间)中。你是否真的需要每次在递归时创建nothing的新副本?使用包含两个简单的相互递归函数的命名空间可能会更好。

答案 4 :(得分:12)

最简单的方法是使用“匿名对象”:

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

您的全球空间完全没有受到污染。这很简单。您可以轻松利用对象的非全局状态。

答案 5 :(得分:11)

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

答案 6 :(得分:6)

您可以执行以下操作:

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

答案 7 :(得分:3)

当您声明这样的匿名函数时:

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

它被认为是一个函数表达式,它有一个可选的名称(可以用来从内部调用它。但是因为它是一个函数表达式(而不是一个语句)它保持匿名(但是有一个你可以调用的名字)因此,这个函数可以调用自己:

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

答案 8 :(得分:3)

为什么不将函数传递给函数本身?

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

答案 9 :(得分:3)

在某些情况下,您必须依赖匿名函数。给定是一个递归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)(...) - 这就是全部。

答案 10 :(得分:2)

我不确定答案是否仍然需要,但这也可以使用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。

答案 11 :(得分:1)

像bobince写的那样,只需命名你的功能。

但是,我猜你也想传递一个初始值并最终停止你的功能!

var initialValue = ...

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

working jsFiddle example (uses data += data for fun)


答案 12 :(得分:1)

我需要(或者更确切地说,想要)一个单行匿名函数来向上移动一个构建字符串的对象,并像这样处理它:

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

产生一个像'Root:foo:bar:baz:...'

这样的字符串

答案 13 :(得分:1)

使用ES2015,我们可以使用语法和滥用默认参数和thunk来玩一下。后者只是没有任何参数的函数:

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是一个命名函数,可以递归调用自身。

答案 14 :(得分:0)

我不建议在任何实际用例中执行此操作,但作为一个有趣的练习,您实际上可以使用第二个匿名函数来执行此操作!

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

这种工作方式是我们将匿名函数作为参数传递给自身,因此我们可以从自身调用它。

答案 15 :(得分:0)

另一个使用rosetta-code链接的Y组合器解决方案(我认为以前有人在stackOverflow的某处提到了该链接。

箭头是我更容易理解的匿名函数:

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

答案 16 :(得分:0)

这是带有箭头功能的@ zem答案版本。

您可以使用UY组合子。 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'))

答案 17 :(得分:0)

这是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

无需展开第一次递归。接收自身作为参考的函数可以追溯到OOP的原始渗出。

答案 18 :(得分:0)

另一个不涉及命名函数或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

答案 19 :(得分:-1)

这可能无处不在,但您可以使用arguments.callee来引用当前函数。

因此,可以这样做因子:

var fac = function(x) { 
    if (x == 1) return x;
    else return x * arguments.callee(x-1);
}