簡體   English   中英

如何在 ES6 中遞歸地編寫箭頭函數?

[英]How do I write an arrow function in ES6 recursively?

ES6 中的箭頭函數沒有arguments屬性,因此arguments.callee將不起作用,並且無論如何都不會在嚴格模式下工作,即使只是使用匿名函數。

箭頭函數不能命名,因此不能使用命名函數表達式技巧。

那么...如何編寫遞歸箭頭函數? 那是一個箭頭函數,它根據某些條件遞歸調用自己,當然等等?

編寫一個不命名的遞歸函數是一個與計算機科學本身一樣古老的問題(實際上更古老,因為 λ-演算早於計算機科學),因為在 λ-演算中所有函數都是匿名的,但您仍然需要遞歸。

解決方案是使用固定點組合器,通常是 Y 組合器。 這看起來像這樣:

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

這將遞歸計算5的階乘。

注意:代碼主要基於此: Y Combinator 用 Ja​​vaScript 解釋 所有的功勞都應該歸原作者所有。 我主要只是“協調”(這就是你所說的用 ES/Harmony 的新功能重構舊代碼?)它。

看起來您可以將箭頭函數分配給變量並使用它來遞歸調用該函數。

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

Claus Reinke 在esdiscuss.org 網站上的討論中回答了您的問題。

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

使用您分配函數的變量,例如

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

如果您真的需要匿名,請使用Y 組合器,如下所示:

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) …

丑是不是?

特爾;博士:

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

這里有很多答案,其中有一個適當的 Y 的變化——但這有點多余......問題是 Y 的通常解釋方式是“如果沒有遞歸會怎樣”,所以 Y 本身不能指代自己。 但是因為這里的目標是一個實用的組合器,所以沒有理由這樣做。 這個答案使用自身定義了rec ,但它很復雜而且有點丑,因為它添加了一個參數而不是柯里化。

簡單的遞歸定義的 Y 是

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

但由於 JS 並不懶惰,因此上面添加了必要的包裝。

用於任意數量參數的遞歸函數定義的通用組合器(不使用自身內部的變量)將是:

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

由於arguments.callee是一個糟糕的選擇,因為棄用/在嚴格模式下不起作用,並且做類似var func = () => {}的事情也很糟糕,這個答案中描述的黑客可能是你唯一的選擇:

javascript:遞歸匿名函數?

var rec = () => {rec()};
rec();

那會是一個選擇嗎?

我發現提供的解決方案真的很復雜,老實說,其中任何一個都看不懂,所以我自己想出了一個更簡單的解決方案(我確定它已經知道了,但我的思考過程是這樣的):

所以你在做一個階乘函數

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 => x == 0 default是滿足endCondition給出的最后一個值,因此x => x operation只是您在每次遞歸時所做的操作,例如乘以 Factorial 或添加 Fibonacci: x1 => x2 => x1 + x2 最后nextStep是傳遞給函數的下一個值,通常是當前值減一: x => x - 1 申請:

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

這是這個答案的一個版本, 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'))

您可以將您的函數分配給 iife 中的變量

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

countdown(3)

//3
//2
//1
//0

我認為最簡單的解決方案是查看您唯一沒有的東西,即對函數本身的引用。 因為如果你有那么回避是微不足道的。

令人驚訝的是,這可以通過高階函數實現。

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)

下面是遞歸函數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});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM