zoukankan      html  css  js  c++  java
  • 尾递归的调用和柯里化——————函数式编程

    尾调用是函数式编程的一个重要概念,本身非常简单,一句话就是某个函数的最后一步是调用另一个函数(仅仅调用第一个函数,不用有任何其他操作,否则不属于尾递归)

    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标准入门(第三版)

  • 相关阅读:
    【shell】 for循环
    【shell】case语句
    【shell】if语句
    【shell】nmap工具的使用
    spring3 循环依赖
    spring3 DI基础
    spring3系列一
    正则表达式学习网址
    常用正则表达式
    hibernate延迟加载
  • 原文地址:https://www.cnblogs.com/czy960731/p/9306090.html
Copyright © 2011-2022 走看看