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

    我个人觉得这是相当晦涩难懂的。。。

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