javascript:递归匿名函数?

Inc*_*ito 112 javascript recursion scope anonymous-function

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

function recur(data) {
    data = data+1;
    var nothing = function() {
        recur(data);
    }
    nothing();
}
Run Code Online (Sandbox Code Playgroud)

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

(function(data){
    data = data+1;
    var nothing = function() {
        //Something here that calls the function?
    }
    nothing();
})();
Run Code Online (Sandbox Code Playgroud)

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

Poi*_*nty 138

可以给功能的名字,甚至当你正在创建的函数作为值,而不是一个"函数声明"的声明.换一种说法:

(function foo() { foo(); })();
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

asyncThingWithCallback(params, (function() {
  function recursive() {
    if (timeToStop())
      return whatever();
    recursive(moreWork);
  }
  return recursive;
})());
Run Code Online (Sandbox Code Playgroud)

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


zem*_*zem 31

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

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

(Y(function(recur) {
  return function(data) {
    data = data+1;
    var nothing = function() {
      recur(data);
    }
    nothing();
  }
})());
Run Code Online (Sandbox Code Playgroud)

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

  • *"关于这个解决方案最值得注意的是你不应该使用它."*为什么? (15认同)
  • 它不会很快.实际使用它很难看(虽然在概念上很漂亮!).你不必给你的函数一个标签或变量名称(我不明白为什么这将是一个问题),但你仍然给它一个名称作为传递给Y的外部函数的参数.所以你不要通过所有这些麻烦获得任何收获. (7认同)

Tha*_*you 19

U组合子

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

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

const U = f => f (f)

U (f => (console.log ('stack overflow imminent!'), U (f)))
Run Code Online (Sandbox Code Playgroud)

我们可以使用各种技术来阻止无限递归.在这里,我将编写我们的匿名函数来返回另一个等待输入的匿名函数; 在这种情况下,一些数字.当提供一个数字时,如果它大于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
Run Code Online (Sandbox Code Playgroud)

这里没有立即明白的是我们的函数,当首次使用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
Run Code Online (Sandbox Code Playgroud)

只有这不是直接递归 - 一个使用自己的名称调用自身的函数.我们的定义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)
Run Code Online (Sandbox Code Playgroud)

如何使用U组合器从现有函数中删除自引用

在这里,我将向您展示如何使用递归函数,该函数使用对自身的引用并将其更改为使用U组合子代替自引用的函数

const factorial = x =>
  x === 0 ? 1 : x * factorial (x - 1)
  
console.log (factorial (5)) // 120
Run Code Online (Sandbox Code Playgroud)

现在使用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
Run Code Online (Sandbox Code Playgroud)

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

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

// remove self reference with U combinator
const foo = U (f => x => ... U (f) (nextX) ...)
Run Code Online (Sandbox Code Playgroud)

Y组合子

相关:U和Y组合使用镜像类比解释

在上一节中,我们看到了如何将自引用递归转换为不依赖于使用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)))
Run Code Online (Sandbox Code Playgroud)

现在我们将看到它的用法与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 ()))
Run Code Online (Sandbox Code Playgroud)

现在我将演示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
Run Code Online (Sandbox Code Playgroud)

现在,我们可以看到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
Run Code Online (Sandbox Code Playgroud)


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

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

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

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
Run Code Online (Sandbox Code Playgroud)

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

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

fibonacci (7)仔细看下面的定义.我们会立即将Yfibonacci这必将对01分别.现在斐波那契只是在等待提供的最后一个参数a.当我们递归时,我们必须调用b(而不是x)因为我们的函数是以curry形式.

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
Run Code Online (Sandbox Code Playgroud)


这种模式可用于定义各种函数.下面我们将看到使用定义的两个函数f (a) (b) (x)组合子(f (a,b,x)Y)和衍生物range,reduce.

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
Run Code Online (Sandbox Code Playgroud)


这是所有无名的OMG

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

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 ]
Run Code Online (Sandbox Code Playgroud)

而且你有它 - reduce使用匿名函数递归计算

  • 天哪,javascript 里面有很多 haskell (2认同)

svi*_*gen 14

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

({
  do: function() {
    console.log("don't run this ...");
    this.do();
  }
}).do();
Run Code Online (Sandbox Code Playgroud)

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


bob*_*nce 13

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

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

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

(function foo(data){
    data++;
    var nothing = function() {
        foo(data);
    }
    nothing();
})();
Run Code Online (Sandbox Code Playgroud)

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

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


小智 11

(function(data){
    var recursive = arguments.callee;
    data = data+1;
    var nothing = function() {
        recursive(data)
    }
    nothing();
})();
Run Code Online (Sandbox Code Playgroud)

  • 我希望每个人都投票支持这个(技术上正确的)答案来实现`arguments.callee`的问题:它在严格模式和ES5中是不允许的. (34认同)

Art*_*BIT 6

你可以这样做:

(foo = function() { foo(); })()
Run Code Online (Sandbox Code Playgroud)

或者在你的情况下:

(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);
Run Code Online (Sandbox Code Playgroud)