关于递归:javascript:递归匿名函数?

javascript: recursive anonymous function?

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

1
2
3
4
5
6
7
function recur(data) {
    data = data+1;
    var nothing = function() {
        recur(data);
    }
    nothing();
}

如果我有匿名功能,我怎么能这样做...

1
2
3
4
5
6
7
(function(data){
    data = data+1;
    var nothing = function() {
        //Something here that calls the function?
    }
    nothing();
})();

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


即使您将函数创建为值而不是"函数声明"语句,也可以为函数指定名称。换一种说法:

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

是一个堆栈递归函数。现在,也就是说,可能不会一般不想这样做,因为Javascript的各种实现存在一些奇怪的问题。 (注意—这是一个相当古老的评论; Kangax博客文章中描述的一些/很多/所有问题可能会在更现代的浏览器中修复。)

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

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

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

1
2
3
4
5
6
7
8
asyncThingWithCallback(params, (function() {
  function recursive() {
    if (timeToStop())
      return whatever();
    recursive(moreWork);
  }
  return recursive;
})());

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


人们在评论中谈到了Y组合,但没有人把它作为答案。

Y组合器可以在javascript中定义如下:(感谢steamer25的链接)

1
2
3
4
5
6
7
8
9
var Y = function (gen) {
  return (function(f) {
    return f(f);
  }(function(f) {
    return gen(function() {
      return f(f).apply(null, arguments);
    });
  }));
}

当你想传递你的匿名函数时:

1
2
3
4
5
6
7
8
9
(Y(function(recur) {
  return function(data) {
    data = data+1;
    var nothing = function() {
      recur(data);
    }
    nothing();
  }
})());

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


U组合子

U组合器接受一个函数并将其应用于自身。所以你给它的功能应该至少有一个参数将绑定到函数(本身)

在下面的示例中,我们没有退出条件,因此我们将无限循环,直到发生堆栈溢出

1
2
3
const U = f => f (f)

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

我们可以使用各种技术来阻止无限递归。在这里,我将编写我们的匿名函数来返回另一个等待输入的匿名函数;在这种情况下,一些数字。当提供一个数字时,如果它大于0,我们将继续重复,否则返回0。

1
2
3
4
5
6
7
8
9
10
11
12
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(匿名函数)有效地构造递归函数

1
2
3
4
5
6
7
8
9
10
11
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的定义并没有引用它自身的内部,仍然可以进行递归

1
2
3
4
5
6
7
8
9
10
11
12
// 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组合子代替自引用的函数

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

现在使用U组合器将内部引用替换为factorial

1
2
3
4
5
6
const U = f => f (f)

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

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

基本的替代模式是这样的。请记住,我们将在下一节中使用类似的策略

1
2
3
4
5
// self reference recursion
const foo =         x => ...   foo (nextX) ...

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

Y组合子

related: the U and Y combinators explained using a mirror analogy

Ok.

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

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

1
2
3
4
5
6
7
8
// 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的比较。请注意,要重复,我们可以简单地调用f ()而不是U (f)

1
2
3
4
5
const U = f => f (f)

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

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

现在我将使用Y演示countDown程序 - 你会看到程序几乎完全相同但是Y组合器让事情变得更清洁了

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
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

具有多于1个参数的U和Y组合子

在上面的例子中,我们看到了如何循环和传递参数以跟踪计算的"状态"。但是,如果我们需要跟踪其他状态呢?

我们可以使用像数组之类的复合数据......

1
2
3
4
5
6
7
8
9
10
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的定义。我们立即应用分别绑定到ab01。现在斐波那契只是在等待提供的最后一个参数,它将绑定到x。当我们递归时,我们必须调用f (a) (b) (x)(不是f (a,b,x)),因为我们的函数是curry形式。

1
2
3
4
5
6
7
8
9
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的导数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

因为我们在这里使用纯函数,所以我们可以用任何命名函数替换它的定义。观察当我们采用斐波那契并用其表达式替换命名函数时会发生什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* 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

你有它 - fibonacci (7)使用匿名函数递归计算

好。


使用"匿名对象"可能最简单:

1
2
3
4
5
6
({
  do: function() {
    console.log("don't run this ...");
    this.do();
  }
}).do();

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


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

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

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

1
2
3
4
5
6
7
(function foo(data){
    data++;
    var nothing = function() {
        foo(data);
    }
    nothing();
})();

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

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


1
2
3
4
5
6
7
8
(function(data){
    var recursive = arguments.callee;
    data = data+1;
    var nothing = function() {
        recursive(data)
    }
    nothing();
})();


你可以这样做:

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

或者在你的情况下:

1
2
3
4
5
6
7
8
(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);


在某些情况下,您必须依赖匿名函数。给定是一个递归map函数:

1
2
3
4
5
6
7
8
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包装到另一个函数中,例如:

1
2
3
4
5
6
7
const map = f => xs => {
  let next = acc => ([head, ...tail]) => head === undefined
   ? acc
   : map ([...acc, f(head)]) (tail);

  return next([])(xs);
}

但这个解决方案非常冗长。让我们使用低估的U组合子:

1
2
3
4
5
6
7
8
9
10
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)(...) - 就是全部。


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

1
2
3
(function () {
    // Pass
}());

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

1
2
3
4
(function foo () {
    foo();
}());
foo //-> undefined


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

1
2
3
4
5
6
7
8
    var functionCaller = function(thisCaller, data) {
        data = data + 1;
        var nothing = function() {
            thisCaller(thisCaller, data);
        };
        nothing();
    };
    functionCaller(functionCaller, data);

我不确定答案是否仍然需要,但这也可以使用function.bind创建的委托来完成:

1
2
3
4
5
6
7
8
9
10
11
12
    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。


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

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

它产生一个像'Root:foo:bar:baz:...'的字符串


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

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

1
2
3
4
5
6
7
8
9
10
11
12
var initialValue = ...

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

工作jsFiddle示例(使用数据+ =数据的乐趣)



使用ES2015,我们可以使用语法和滥用默认参数和thunk进行一些操作。后者只是没有任何参数的函数:

1
2
3
4
5
6
7
8
9
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)作为其默认值的参数。当applyT调用f时,必须在没有参数的情况下进行此调用,以便使用默认值。默认值是一个函数,因此f是一个命名函数,它可以递归调用自身。


还有另一个Y-combinator解决方案,使用rosetta-code链接(我想之前有人在stackOverflow上提到了某个链接。

箭头用于匿名函数对我来说更具可读性:

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

这是jforjs答案的返工,具有不同的名称和略微修改的条目。

1
2
3
4
5
6
7
8
9
10
11
12
// 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的原始渗出。


这是带有箭头功能的@ 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'))


另一个不涉及命名函数或arguments.callee的答案

1
2
3
4
5
6
7
8
9
10
11
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来引用当前函数。

所以,因子可以这样做:

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