zoukankan      html  css  js  c++  java
  • 函数式编程常用核心概念

    •纯函数

    •函数的柯里化

    •函数组合

    •Point Free

    •声明式与命令式代码

    •核心概念

    1.纯函数

    什么是纯函数呢?

    对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态的函数,叫做纯函数。

    举个栗子:

    1
    2
    3
    4
    5
    var xs = [1,2,3,4,5];// Array.slice是纯函数,因为它没有副作用,对于固定的输入,输出总是固定的
    xs.slice(0,3);
    xs.slice(0,3);
    xs.splice(0,3);// Array.splice会对原array造成影响,所以不纯
    xs.splice(0,3);

      

    2.函数柯里化

    传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

    我们有这样一个函数checkage:

    1
    var min = 18; <br>var checkage = age => age > min;

      

    这个函数并不纯,checkage 不仅取决于 age还有外部依赖的变量 min。 纯的 checkage 把关键数字 18 硬编码在函数内部,扩展性比较差,柯里化优雅的函数式解决。

    1
    2
    3
    var checkage = min => (age => age > min);
     
    var checkage18 = checkage(18); // 先将18作为参数,去调用此函数,返回一个函数age => age > 18;
    1
    checkage18(20);// 第二步,上面返回的函数去处理剩下的参数,即 20 => 20 > 18; return true;

      

     再看一个例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 柯里化之前
    function add(x, y) {
        return x + y;
    }
    add(1, 2) // 3
    // 柯里化之后
    function addX(y) {
        return function (x) {
            return x + y;
        };
    }
    addX(2)(1) // 3

      

      事实上柯里化是一种“预加载”函数的方法,通过传递较少的参数,得到一个已经记住了这些参数的新函数,某种意义上讲,这是一种对参数的“缓存”,是一种非常高效的编写函数的方法。

    3.函数组合

    为了解决函数嵌套过深,洋葱代码:h(g(f(x))),我们需要用到“函数组合”,我们一起来用柯里化来改他,让多个函数像拼积木一样。

    1
    2
    3
    4
    5
    const compose = (f, g) => (x => f(g(x)));
    var first = arr => arr[0];
    var reverse = arr => arr.reverse();
    var last = compose(first, reverse);
    last([1, 2, 3, 4, 5]); // 5

      

    函数组合交换律,类似于乘法交换律:

    4.Point Free

    把一些对象自带的方法转化成纯函数,不要命名转瞬即逝的中间变量。
    大家看一下下面的函数:
    1
    const f = str => str.toUpperCase().split(' ');
    这个函数中,我们使用了 str 作为我们的中间变量,但这个中间变量除了让代码变得长了一点以外是毫无意义的。
     
    下面我们用函数组合去改造一下:
    1
    2
    3
    4
    var toUpperCase = word => word.toUpperCase();
    var split = x => (str => str.split(x));
    var f = compose(split(' '), toUpperCase);
    f("abcd efgh");

      

    把一些对象自带的方法转化成纯函数,然后通过函数组合去调用,这种风格能够帮助我们减少不必要的命名,让代码保持简洁和通用。是不是很方便!

    5.声明式与命令式代码

    在我们日常业务开发中,写的代码绝大多数都为命令式代码;

    我们通过编写一条又一条指令去让计算机执行一些动作,这其中一般都会涉及到很多繁杂的细节。
    而声明式就要优雅很多了,我们通过写表达式的方式来声明我们想干什么,而不是通过一步一步的指示。
    1
    2
    3
    4
    5
    6
    7
    //命令式
    let CEOs = [];
    for (var i = 0; i < companies.length; i++) {
        CEOs.push(companies[i].CEO)
    }
    //声明式
    let CEOs = companies.map(c => c.CEO);

      

    函数式编程的一个明显的好处就是这种声明式的代码,对于无副作用的纯函数,我们完全可以不考虑函数内部是如何实现的,专注于编写业务代码。优化代码时,目光只需要集中在这些稳定坚固的函数内部即可。相反,不纯的函数式的代码会产生副作用或者依赖外部系统环境,使用它们的时候总是要考虑这些不干净的副作用。在复杂的系统中,这对于我们的心智来说是极大的负担。

    6.核心概念

    下面我们再深入一下,大家注意好好理解吸收:

    高阶函数

    高阶函数,就是把函数当参数,把传入的函数做一个封装,然后返回这个封装函数,达到更高程度的抽象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //命令式
    var add = function (a, b) {
        return a + b;
    };
     
    function math(func, array) {
        return func(array[0], array[1]);
    }
    math(add, [1, 2]); // 3

      

    递归与尾递归

    指函数内部的最后一个动作是函数调用。 该调用的返回值, 直接返回给函数。 函数调用自身, 称为递归。 如果尾调用自身, 就称为尾递归。 递归需要保存大量的调用记录, 很容易发生栈溢出错误, 如果使用尾递归优化, 将递归变为循环, 那么只需要保存一个调用记录, 这样就不会发生栈溢出错误了。通俗点说,尾递归最后一步需要调用自身,并且之后不能有其他额外操作。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 不是尾递归,无法优化
    function factorial(n) {
        if (n === 1) return 1;
        return n * factorial(n - 1);
    }
     
    function factorial(n, total) {
        if (n === 1) return total;
        return factorial(n - 1, n * total);
    //ES6强制使用尾递归

      

    我们看一下递归和尾递归执行过程:

    递归:

    1
    2
    3
    4
    function sum(n) {
        if (n === 1) return 1;
        return n + sum(n - 1);
    }

      

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    sum(5)
    (5 + sum(4))
    (5 + (4 + sum(3)))
    (5 + (4 + (3 + sum(2))))
    (5 + (4 + (3 + (2 + sum(1)))))
    (5 + (4 + (3 + (2 + 1))))
    (5 + (4 + (3 + 3)))
    (5 + (4 + 6))
    (5 + 10)
    15 // 递归非常消耗内存,因为需要同时保存很多的调用帧,这样,就很容易发生“栈溢出”

      

    尾递归

    1
    2
    3
    4
    5
    6
    function sum(x, total) {
        if (x === 1) {
            return x + total;
        }
        return sum(x - 1, x + total);
    }

      

    1
    2
    3
    4
    5
    6
    sum(5, 0)
    sum(4, 5)
    sum(3, 9)
    sum(2, 12)
    sum(1, 14)
    15

      

    整个计算过程是线性的,调用一次sum(x, total)后,会进入下一个栈,相关的数据信息和跟随进入,不再放在堆栈上保存。当计算完最后的值之后,直接返回到最上层的sum(5,0)。这能有效的防止堆栈溢出。 在ECMAScript 6,我们将迎来尾递归优化,通过尾递归优化,javascript代码在解释成机器码的时候,将会向while看起,也就是说,同时拥有数学表达能力和while的效能。
  • 相关阅读:
    佛山Uber优步司机奖励政策(2月1日~2月7日)
    长沙Uber优步司机奖励政策(2月1日~2月7日)
    广州Uber优步司机奖励政策(2月1日~2月7日)
    西安Uber优步司机奖励政策(2月1日~2月7日)
    武汉Uber优步司机奖励政策(2月1日~2月7日)
    Apicloud自定义模块
    Android Studio导出jar包
    android studio 、 as 如何导入eclipse项目
    安卓 使用Gradle生成正式签名apk文件
    如何用Android studio生成正式签名的APK文件
  • 原文地址:https://www.cnblogs.com/blueball/p/11358877.html
Copyright © 2011-2022 走看看