Functional-Light-JS ---- Recursion

遞歸

  1. 遞歸的定義
  2. 遞歸的好處
  3. 互遞歸
  4. 遞歸的弊端
  5. 尾調用
  6. 遞歸的優化
遞歸的定義

遞歸就是一個函數在其內部調用它自己,同時有一個條件來終止這個遞歸的循環調用。

遞歸的好處

遞歸可以使我們的代碼更加的接近聲明式的代碼,可讀性會更加好一些。

	The most commonly cited reason that recursion fits the spirit of FP is because it trades (much of) 
	the explicit tracking of state with implicit state on the call stack. Typically, recursion is most useful
	when the problem requires conditional branching and back-tracking, and managing that kind
	of state in a purely iterative environment can be quite tricky; at a minimum, the code is highly 
	imperative and harder to read and verify. But  tracking each level of branching as its own scope 
	on the call stack often significantly cleans up the readability of the code.
互遞歸

互遞歸(Mutual Rescursion)就是當兩個或多個函數在遞歸循環中相互調用時,這稱爲互遞歸。
example:

	function isOdd(v) {
	    if (v === 0) return false;
	    return isEven( Math.abs( v ) - 1 );
	}
	
	function isEven(v) {
	    if (v === 0) return true;
	    return isOdd( Math.abs( v ) - 1 );
	}
遞歸的弊端

遞歸可能導致堆(stack)內存泄露。

尾調用

當函數的最後一步是函數調用時,我們稱之爲尾調用(tail call)。在ES6中有個一針對尾調用的概念,叫PTC(Proper Tail Calls)即合適的尾調用,JS引擎會針對這種調用進行優化。ES6中的尾遞歸優化還有一個重要前提,就是必須在嚴格模式下纔可以。還有一個概念叫尾調用優化,英文原文是Tail Call Optimizations (TCO)。

//These are not PTC:
foo();
return;

// or
var x = foo( .. );
return x;

// or
return 1 + foo( .. );

//PTC
return x ? foo( .. ) : bar( .. );
遞歸的優化
  1. 將遞歸轉換爲尾調用優化的形式
    這種優化很好理解,就是在可能的情況下,將我們的函數轉換爲尾調用的形式。

  2. 使用蹦牀函數
    蹦牀(Trampolines)函數的本質就是隻要返回一個函數,循環就會繼續,執行該函數並捕獲它的返回,然後檢查它的類型。一旦非函數返回,蹦牀就假定函數調用已經完成,並返回值。

    function trampoline(fn) {
        return function trampolined(...args) {
            var result = fn( ...args );
    
            while (typeof result == "function") {
                result = result();
            }
    
            return result;
        };
    }
    
    var sum = trampoline(
        function sum(num1,num2,...nums) {
            num1 = num1 + num2;
            if (nums.length == 0) return num1;
            return () => sum( num1, ...nums );
        }
    );
    
    var xs = [];
    for (let i=0; i<20000; i++) {
        xs.push( i );
    }
    
    sum( ...xs );                   // 199990000
    

    使用蹦牀函數可以可以調出尾遞歸優化的限定,運行也很快

  3. 使用連續傳遞函數的形式
    連續傳遞函數(Continuation Passing Style),這個優化的本質還是使用了尾調用優化。組織代碼,使每個函數接收另一個函數在其末尾執行,這稱爲延續傳遞樣式(CPS)。
    這是一個針對fibco數列的遞歸優化示例:

    "use strict";
    
    function fib(n,cont = identity) {
        if (n <= 1) return cont( n );
        return fib(
            n - 2,
            n2 => fib(
                n - 1,
                n1 => cont( n2 + n1 )
            )
        );
    }
    

    我個人覺得這是相當晦澀難懂的。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章