尾遞歸的調用和柯里化——————函數式編程


尾調用是函數式編程的一個重要概念,本身非常簡單,一句話就是某個函數的最后一步是調用另一個函數(僅僅調用第一個函數,不用有任何其他操作,否則不屬於尾遞歸)

1.尾遞歸不一定出現在函數尾部,只要是最后一步操作即可

function f(x) {
    
    if(x>0){
        return m(x);
    }
    return n(x);
}

上面的代碼中,函數m和n都屬於尾調用,因為他們都是函數f的最后一步操作。

2.遞歸幀和尾遞歸的優化

每一個我們進行遞歸,就會有一個"遞歸幀"壓如棧空間,如A函數遞歸調用B函數,那么A函數的遞歸幀會壓棧,直到B函數釋放棧空間,所以遞歸是一個十分耗費內存的操作,如斐波那契數列

function fib() {
    if(n===1 || n===2){
        return 1;
    }
    
    return f(n-1)+f(n-2);
}
fib(10) // 55
fib(100) // 棧滿

尾調用由於是函數的最后一步操作,所以不需要保留外層函數的調用幀,因為調用位置,內部變量等信息不會再使用了,直接用內函數的調用幀取代外層函數即可,上代碼

function fib(n,ca1=1,ca2=1){

    if(n===1 || n===2){
        return ca2;
    }

    return fib(n-1,ca2,ca1+ca2);
}
console.log(fib(100)); //354224848179262000000

這就是尾遞歸的優化,每次執行時棧內只有一個幀,將大大的節省內存,這就是尾遞歸的調用和優化。
“尾遞歸優化"對遞歸操作意義重大,所以一些函數式編程語言將其寫入了規格,es6就是如此,ECMAScript的實現都必須實現尾遞歸優化。

3.遞歸函數的改寫和柯里化(currying)

尾遞歸的實現往往需要改寫遞歸函數,確保最后一步只調用自身。做到這一點,就要把所有用到的內部變量改寫成函數的參數,上面已經可以看到,這么做的缺點是不太直觀,有了兩個方法可以解決這個問題。

  方法一,柯里化概念,就是將多參數的函數轉化成為單參數的形式,其實加上其他函數,柯里化函數只處理第一個函數,其他的函數處理其他參數,舉例如下

function currying(fn,n) {
    return function (m) {
        return fn.call(this,m,n);
    }
}

function tailFactorial(n,total) {
    if(n===1) return total;

    return tailFactorial(n-1,n*total);
}

const factorial = currying(tailFactorial,1);

console.log(factorial(5));

將尾遞歸函數tailFactorial變為接受1個參數的factorial

   方法二,使用es6的函數默認值

function factorial(n,total=1) {
    
    if(n===1) return total;
    
    return factorial(n-1,total*n);
}
factorial(5)//120

因為默認值給了1,所以調用時不用提供這個值。

4.嚴格模式

es6的尾調用優化,只在嚴格模式下開啟,正常模式下是無效的,這是因為,正常模式下函數內部有兩個變量,可以跟蹤函數的調用棧

func.arguments:返回調用函數的參數(arguments.callee 得到函數) func.caller: 返回調用當前函數的那個函數。

function fib(n,ca1=1,ca2=1){

    if(n===1 || n===2){
        return ca2;
    }
    console.log(fib.caller);
    console.log(fib.arguments);
    return fib(n-1,ca2,ca1+ca2);
}
console.log(fib(5));
[Function]
{ '0': 5 }
[Function: fib]
{ '0': 4, '1': 1, '2': 2 }
[Function: fib]
{ '0': 3, '1': 2, '2': 3 }
5

可以看得到調用的參數越來越多。

5.自己實現尾遞歸的優化

我們如何在正常環境下,進行尾遞歸的優化呢?原理非常簡單,尾遞歸之所以需要優化,就是調用太多造成棧溢出,那就要減少調用棧,用循環代替遞歸。

參考文獻:

  阮一峰:es6標准入門(第三版)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM