递归匿名函数? ?

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

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

我想要一种方法来调用这个函数的函数... ... 我曾经在某个地方看到过脚本(我不记得在哪里了) ,它能告诉你一个被调用的函数的名称,但是我现在想不起任何信息。

82213 次浏览
(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 foo() { foo(); })();

是一个叠加吹递归函数。也就是说,您通常使用 可能不想这样做,因为各种 Javascript 实现都存在一些奇怪的问题。(这是一个相当老的评论,Kangax 博客文章中描述的一些/许多/所有问题可能在更现代的浏览器中得到解决。)

当你给一个这样的名字,名字是不可见的函数之外(好吧,它不应该是; 这是一个奇怪的地方)。就像 Lisp 里的“ letrec”。

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

编辑 & mash; 如果你想拥有一个可以调用自己的“匿名”函数的效果,你可以这样做(假设你传递的函数是一个回调函数或类似的东西) :

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

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

我不会将其作为内联函数执行。这是在挑战好品味的极限,而且不会给你带来任何好处。

如果你真的必须这么做,那么就像 Fabrizio 的答案一样,有 ABc0。然而,这通常被认为是不可取的,并且在 ECMAScript 第五版的“严格模式”中是不允许的。虽然 ECMA3和非严格模式不会消失,但在严格模式下工作可能会带来更多的语言优化。

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

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

然而,命名内联函数表达式也是最好避免的,因为 IE 的 JScript 对它们做了一些坏事。在上面的示例中,foo不正确地污染了 IE 中的父范围,而父 foofoo中看到的 foo的一个单独的实例。

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

就像 bobince 写的那样,简单地命名你的函数。

但是,我猜想您还想传入一个初始值,并最终停止您的函数!

var initialValue = ...


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

工作 jsFiddle 示例(使用 data + = data 作为乐趣)


当你像这样声明一个匿名函数:

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

它被认为是一个函数表达式,并且有一个可选的名称(您可以使用该名称在其内部调用它。但是因为它是一个函数表达式(而不是一个语句) ,所以它保持匿名(但是有一个可以调用的名称)。因此这个函数可以调用它自己:

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

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

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

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

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

使用“匿名对象”可能是最简单的:

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

你的全球空间是完全没有被污染的。它非常简单。而且你可以很容易地利用物体的非全球状态。

还可以使用 ES6对象方法使语法更简洁。

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

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

另一个不涉及命名函数或 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来引用当前函数。

因此,factorial 可以这样做:

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

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

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

它生成一个类似于‘ Root: foo: bar: baz: ...’的字符串

这是 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 的原始漏洞。

在某些情况下,你必须依赖匿名函数:

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)(...)-仅此而已。

这是@zem 答案的一个版本,带有箭头函数。

您可以使用 U或者 Y组合器。 Y 组合器是最简单的组合器。

使用 U组合器,必须不断传递函数: Const U = f = > f (f) U (self Fn = > arg = > self Fn (self Fn)(’到无穷大和超越’))

使用 Y组合器,你不必一直传递函数: Const Y = gen = > U (f = > gen ((... args) = > f (f)(... args))) Y (self Fn = > arg = > self Fn (‘ to ∞ and Beyond’))

密码 U

通过将函数作为参数传递给函数本身,函数可以使用它的参数而不是它的名称重现!因此,给予 U的函数应该至少有一个将绑定到函数(它自己)的参数。

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

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


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 结合器

相关资料: 用镜像类比解释 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 组合子。注意,为了重复使用,我们可以简单地调用 f ()而不是 U (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 ()))

现在我将演示使用 YcountDown程序-您将看到程序几乎相同,但 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

如您所见,f成为递归本身的机制。为了重现,我们像普通函数一样调用它。我们可以用不同的参数多次调用它,结果仍然是正确的。由于它是一个普通的函数参数,我们可以给它起任何我们喜欢的名字,比如下面的 recur-

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


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

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

我们 可以使用复合数据,比如数组什么的。

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)得到我们想要的答案就好了。

利用我们对咖喱函数(一元(1参数)函数序列)的了解,我们可以很容易地实现我们的目标,而不必修改 Y的定义或依赖于复合数据或高级语言特性。

仔细看看下面 fibonacci的定义。我们立即应用 01,它们分别绑定到 ab。现在 fibonacci 只是在等待提供最后一个参数,这个参数将绑定到 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 ]


都是匿名的

因为我们在这里使用的是纯函数,所以我们可以用任何命名的函数来代替它的定义。看看当我们使用 fibonacci 并用它们的表达式替换命名函数时会发生什么

/* 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)只用匿名函数递归计算

使用 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)。当 applyT调用 f时,此调用必须在没有参数的情况下进行,以便使用默认值。默认值是一个函数,因此 f是一个命名函数,它可以递归地调用自己。

还有一个 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);
});

它的工作方式是我们将匿名函数作为参数传递给它自己,所以我们可以从它自己调用它。

详细信息请访问这个网址: 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();
})();