【问题标题】:How do I write an arrow function in ES6 recursively?如何在 ES6 中递归地编写箭头函数?
【发布时间】:2014-10-03 09:40:17
【问题描述】:

ES6 中的箭头函数没有 arguments 属性,因此 arguments.callee 将不起作用,并且即使只使用匿名函数,也不会在严格模式下工作。

箭头函数不能命名,所以不能使用命名函数表达式技巧。

那么...如何编写递归箭头函数?那是一个箭头函数,它会根据某些条件递归调用自己,当然等等?

【问题讨论】:

  • 你可以使用常用函数,可能箭头函数不是递归调用的正确工具。
  • 如果使用三元运算符,阶乘函数就是一个语句。单语句匿名函数当然是箭头函数的用例。
  • 为什么不将函数分配给一个变量,而该变量又在函数体的范围内?
  • 那不会很健壮。如果将函数分配给不同的变量并将原始变量重新分配给新值,则您的代码会中断。这通常可以使用命名函数表达式来解决,但箭头函数不能命名,至少不在它们自己的范围内。
  • 如果使用const,那么原来的不能重新赋值。看起来非常健壮,事实上,现在已被广泛使用。

标签: javascript recursion ecmascript-6 arrow-functions


【解决方案1】:

编写一个递归函数而不命名它是一个与计算机科学本身一样古老的问题(实际上甚至更早,因为 λ-演算早于计算机科学),因为在 λ-演算中所有函数是匿名的,但你仍然需要递归。

解决方案是使用定点组合器,通常是 Y 组合器。这看起来像这样:

(y => 
  y(
    givenFact => 
      n => 
        n < 2 ? 1 : n * givenFact(n-1)
  )(5)
)(le => 
  (f => 
    f(f)
  )(f => 
    le(x => (f(f))(x))
  )
);

这将递归计算5 的阶乘。

注意:代码很大程度上基于此:The Y Combinator explained with JavaScript。所有功劳应归原作者所有。我大多只是“协调”(这就是你所说的用 ES/Harmony 的新功能重构旧代码吗?)它。

【讨论】:

  • 说真的……如果有人真的使用 Y 组合子而不是命名函数……(对一个可怕前提的问题的好答案 ;-))
  • second 的答案应该是这里接受的答案。
  • @impinball:该答案将函数分配给 OP 不允许的变量。我知道那很愚蠢,你知道那很愚蠢,每个人都知道那很愚蠢,但这就是要求。
  • 我更喜欢“harmonize”而不是“harmonicalize”,原因与我更喜欢“canonize”而不是“canonicalize”的原因相同。
  • 这看起来很可怕。
【解决方案2】:

看起来您可以将箭头函数分配给变量并使用它来递归调用函数。

var complex = (a, b) => {
    if (a > b) {
        return a;
    } else {
        complex(a, b);
    }
};

【讨论】:

  • complex(2,3)InternalError: too much recursion
  • 是的,它只是一个显示递归调用的简单函数。
  • 这将函数与名称结合起来。坏主意。
  • @OrtomalaLokni 为什么他必须使它成为一个有效的阶乘函数?
  • 太荒谬了。递归函数的任何示例都是该问题的有效答案。提问者不喜欢将功能分配给名称,因此可能不会接受这个答案,但您对 TamilVendhan 的投诉是荒谬的......
【解决方案3】:

Claus Reinke 在esdiscuss.org website 的讨论中回答了您的问题。

在 ES6 中你必须定义他所谓的递归组合器。

 let rec = (f)=> (..args)=> f( (..args)=>rec(f)(..args), ..args )

如果要调用递归箭头函数,则必须以箭头函数为参数调用递归组合器,箭头函数的第一个参数为递归函数,其余为参数。递归函数的名称并不重要,因为它不会在递归组合器之外使用。然后,您可以调用匿名箭头函数。这里我们计算 6 的阶乘。

 rec( (f,n) => (n>1 ? n*f(n-1) : n) )(6)

如果你想在 Firefox 中测试它,你需要使用递归组合器的 ES5 翻译:

function rec(f){ 
    return function(){
        return f.apply(this,[
                               function(){
                                  return rec(f).apply(this,arguments);
                                }
                            ].concat(Array.prototype.slice.call(arguments))
                      );
    }
}

【讨论】:

  • 这看起来很有趣。我认为这可以解决它。
  • 只想清除(.. vs ...):let rec = (f)=&gt; (...args)=&gt; f( (...args)=&gt;rec(f)(...args), ...args ); rec( (f,n) =&gt; (n&gt;1 ? n*f(n-1) : n) )(6);
【解决方案4】:

使用分配函数的变量,例如

const fac = (n) => n>0 ? n*fac(n-1) : 1;

如果您确实需要匿名,请使用Y combinator,如下所示:

const Y = (f) => ((x)=>f((v)=>x(x)(v)))((x)=>f((v)=>x(x)(v)))
… Y((fac)=>(n)=> n>0 ? n*fac(n-1) : 1) …

(ugly, isn't it?)

【讨论】:

  • 非常丑陋,但在概念上如此美丽。
【解决方案5】:

TL;DR:

const rec = f => f((...xs) => rec(f)(...xs));

这里有很多答案,proper Y 有变体——但这有点多余......问题是 Y 的通常解释方式是“如果没有递归怎么办”,所以 Y 本身不能引用对自己。但由于这里的目标是实用 组合器,因此没有理由这样做。 this answer 使用自己定义了 rec,但它很复杂而且有点丑陋,因为它添加了一个参数而不是currying。

简单的递归定义的 Y 是

const rec = f => f(rec(f));

但由于 JS 并不懒惰,因此上面添加了必要的包装。

【讨论】:

  • 这是真正的答案。无需直接跳到完整的 y-combinator 实现,因为我们不是在没有递归的语言中。
【解决方案6】:

用于任意数量参数的递归函数定义的通用组合器(不使用内部变量)将是:

const rec = (le => ((f => f(f))(f => (le((...x) => f(f)(...x))))));

这可以用于定义阶乘:

const factorial = rec( fact => (n => n < 2 ? 1 : n * fact(n - 1)) );
//factorial(5): 120

或字符串反转:

const reverse = rec(
  rev => (
    (w, start) => typeof(start) === "string" 
                ? (!w ? start : rev(w.substring(1), w[0] + start)) 
                : rev(w, '')
  )
);
//reverse("olleh"): "hello"

或按顺序遍历树:

const inorder = rec(go => ((node, visit) => !!(node && [go(node.left, visit), visit(node), go(node.right, visit)])));

//inorder({left:{value:3},value:4,right:{value:5}}, function(n) {console.log(n.value)})
// calls console.log(3)
// calls console.log(4)
// calls console.log(5)
// returns true

【讨论】:

    【解决方案7】:
    var rec = () => {rec()};
    rec();
    

    这会是一个选择吗?

    【讨论】:

    • 请阅读我对这个问题的最后评论。这与分配给它的变量密切相关。将其重新分配给另一个并将 rec 重新分配给其他任何东西,它就会中断。
    • 好吧,我猜箭头函数不能如你所愿,所以你需要使用一个普通的。
    • 把它包在一个闭包里。
    • 但这也需要编写function,并且也不会缩短所需的代码。当然可以在可能有直线的地方绕圈子,但是否有意义是另一个问题。
    【解决方案8】:

    由于arguments.callee 是一个不好的选择,因为它在严格模式下被弃用/不工作,并且做类似var func = () =&gt; {} 的事情也不好,这个答案中描述的黑客可能是你唯一的选择:

    javascript: recursive anonymous function?

    【讨论】:

    • 该属性已弃用,不推荐使用。也不会在严格模式下工作。
    • 而且 ES6 模块在没有选择退出 IIRC 的情况下是隐式严格的。
    • 写“使用严格”;进入我的控制台,然后重新运行代码没有抛出任何错误。编辑:这个评论是错误的,你是对的。
    • @Siddharth Seeing as arguments.callee 是不允许的,而且你不想做类似func = () =&gt; {} 的事情,可能没有任何办法,尽管你可以像在其他 SO 答案中那样使用 hack我链接到了。
    • "var func = () => {}" - 等等,为什么?
    【解决方案9】:

    我发现提供的解决方案非常复杂,老实说无法理解其中任何一个,所以我自己想出了一个更简单的解决方案(我确定它已经知道,但这是我的思考过程):

    所以你正在制作一个阶乘函数

    x => x < 2 ? x : x * (???)
    

    (???) 是函数应该调用自身的位置,但由于您无法命名它,显而易见的解决方案是将其作为参数传递给自身

    f => x => x < 2 ? x : x * f(x-1)
    

    但这行不通。因为当我们调用 f(x-1) 时,我们正在调用这个函数本身,我们只是将它的参数定义为 1) f: 函数本身和 2) x 值。好吧,我们确实有函数本身,f 记得吗?所以先通过吧:

    f => x => x < 2 ? x : x * f(f)(x-1)
                                ^ the new bit
    

    就是这样。我们刚刚创建了一个将自身作为第一个参数的函数,生成了 Factorial 函数!直接把它传给它自己:

    (f => x => x < 2 ? x : x * f(f)(x-1))(f => x => x < 2 ? x : x * f(f)(x-1))(5)
    >120
    

    您可以创建另一个将其参数传递给自身的函数,而不是编写两次:

    y => y(y)
    

    并将你的阶乘生成函数传递给它:

    (y => y(y))(f => x => x < 2 ? x : x * f(f)(x-1))(5)
    >120
    

    轰隆隆。这是一个小公式:

    (y => y(y))(f => x => endCondition(x) ? default(x) : operation(x)(f(f)(nextStep(x))))
    

    对于将数字从 0 加到 x 的基本函数,endCondition 是您需要停止循环的时候,所以 x =&gt; x == 0default 是您在满足 endCondition 时给出的最后一个值,因此 x =&gt; xoperation 只是您在每次递归中执行的操作,例如在阶乘中相乘或在斐波那契中相加:x1 =&gt; x2 =&gt; x1 + x2。最后nextStep 是传递给函数的下一个值,通常是当前值减一:x =&gt; x - 1。申请:

    (y => y(y))(f => x => x == 0 ? x : x + f(f)(x - 1))(5)
    >15
    

    【讨论】:

    • 这被称为U组合子const U = f =&gt; f(f)
    【解决方案10】:

    这是这个答案的一个版本,https://stackoverflow.com/a/3903334/689223,带有箭头功能。

    您可以使用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'))

    【讨论】:

      【解决方案11】:

      您可以将函数分配给 iife 中的变量

      var countdown = f=>(f=a=>{
        console.log(a)
        if(a>0) f(--a)
      })()
      
      countdown(3)
      
      //3
      //2
      //1
      //0
      

      【讨论】:

        【解决方案12】:

        我认为最简单的解决方案是查看您唯一没有的东西,即对函数本身的引用。因为如果你有,那么回避是微不足道的。

        令人惊讶的是,这可以通过高阶函数实现。

        let generateTheNeededValue = (f, ...args) => f(f, ...args);
        

        这个函数正如名字所暗示的,它将生成我们需要的引用。现在我们只需要将它应用到我们的函数中

        (generateTheNeededValue)(ourFunction, ourFunctionArgs)
        

        但是使用这个东西的问题是我们的函数定义需要一个非常特殊的第一个参数

        let ourFunction = (me, ...ourArgs) => {...}
        

        我喜欢把这个特殊的价值称为“我”。现在每次我们需要递归时,我们都会这样做

        me(me, ...argsOnRecursion);
        

        所有这些。我们现在可以创建一个简单的阶乘函数。

        ((f, ...args) => f(f, ...args))((me, x) => {
          if(x < 2) {
            return 1;
          } else {
            return x * me(me, x - 1);
          }
        }, 4)
        
        -> 24
        

        我也喜欢看这一行

        ((f, ...args) => f(f, ...args))((me, x) => (x < 2) ? 1 : (x * me(me, x - 1)), 4)
        

        【讨论】:

        • 所有这些都会给你的函数增加更多的逻辑和考虑。所以我不推荐使用它。仅拥有该参考并改用该参考要简单得多。没有问题。如果有人强迫你,你真的只会使用它,因为我们今天拥有的工具和语言肯定会让我们拥有这种价值。
        【解决方案13】:

        这里是递归函数js es6的例子。

         let filterGroups = [
             {name: 'Filter Group 1'}
         ];
        
        
         const generateGroupName = (nextNumber) => {
            let gN = `Filter Group ${nextNumber}`;
            let exists = filterGroups.find((g) => g.name === gN);
            return exists === undefined ? gN : generateGroupName(++nextNumber); // Important
         };
        
        
         let fg = generateGroupName(filterGroups.length);
         filterGroups.push({name: fg});
        

        【讨论】:

          猜你喜欢
          • 2016-12-21
          • 2019-01-06
          • 1970-01-01
          • 2015-03-14
          • 1970-01-01
          • 1970-01-01
          • 2016-09-07
          • 2022-01-21
          相关资源
          最近更新 更多