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(); })(); |
是一个堆栈递归函数。现在,也就是说,可能不会 strike>一般不想这样做,因为Javascript的各种实现存在一些奇怪的问题。 (注意—这是一个相当古老的评论; Kangax博客文章中描述的一些/很多/所有问题可能会在更现代的浏览器中修复。)
当你给出这样的名字时,这个名字在功能之外是不可见的(好吧,它不应该是;这是奇怪之一)。这就像Lisp中的"letrec"。
对于
编辑—如果你想拥有一个可以调用自身的"匿名"函数的效果,你可以做这样的事情(假设你将函数作为回调或类似的东西传递):
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 |
这里没有立即明白的是我们的函数,当首次使用
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 |
只有这不是直接递归 - 一个使用自己的名称调用自身的函数。我们对
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组合器将内部引用替换为
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的比较。请注意,要重复,我们可以简单地调用
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 ())) |
现在我将使用
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 |
现在我们也会看到
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 |
但这很糟糕,因为它暴露了内部状态(计数器
使用我们对curried函数(一元(1-paramter)函数的序列)的了解,我们可以轻松实现我们的目标,而无需修改
请仔细阅读以下
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 |
这种模式可用于定义各种函数。下面我们将看到使用
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 |
你有它 -
好。
使用"匿名对象"可能最简单:
1 2 3 4 5 6 | ({ do: function() { console.log("don't run this ..."); this.do(); } }).do(); |
您的全球空间完全没有受到污染。这很简单。您可以轻松利用对象的非全局状态。
我不会这样做作为内联函数。它正在推动良好品味的界限,并没有真正得到任何东西。
如果你真的必须,那就像在Fabrizio的回答中那样有
也可以使用命名内联函数:
1 2 3 4 5 6 7 | (function foo(data){ data++; var nothing = function() { foo(data); } nothing(); })(); |
然而,最好避免使用命名的内联函数表达式,因为IE的JScript会给它们带来一些不好的东西。在上面的示例中,
将它放在内联匿名函数中的目的是什么?如果您只是想避免污染父作用域,您当然可以将您的第一个示例隐藏在另一个自调用匿名函数(命名空间)中。你真的需要每次在递归周围创建一个
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); |
在某些情况下,您必须依赖匿名函数。给定是一个递归
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 |
请注意,
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); } |
但这个解决方案非常冗长。让我们使用低估的
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)); |
简洁,不是吗?
当您声明这样的匿名函数时:
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... |
请注意,
还有另一个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的答案版本。
您可以使用
const U = f => f(f)
U(selfFn => arg => selfFn(selfFn)('to infinity and beyond'))
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 |
这可能无处不在,但您可以使用
所以,因子可以这样做:
1 2 3 4 | var fac = function(x) { if (x == 1) return x; else return x * arguments.callee(x-1); } |