• 184 函数式编程


    函数式编程:
    • 1.纯函数
    • 2.函数的柯里化
    • 3.函数组合
    • 4.Point Free : 暂无翻译,意思是不要命名转瞬即逝的中间变量
    • 5.声明式和命令式
     
    一、纯函数:
    纯函数的定义就是:对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。
    这句话听着还是有点云里雾里,我们就以JS的数组来举例说明:
    var arr = [1, 2, 3, 4, 5];
    // slice方法:对原数组
    console.log("slice后:", arr.slice(0, 3), "原数组:", arr); // [1,2,3] , [1, 2, 3, 4, 5]
    console.log("slice后:", arr.slice(0, 3), "原数组:", arr); // [1,2,3] , [1, 2, 3, 4, 5]
    
    var arr = [1, 2, 3, 4, 5];
    // splice方法:
    console.log("splice后:", arr.splice(0, 3), "原数组:", arr); // [1,2,3] , [4,5]
    console.log("splice后:", arr.splice(0, 3), "原数组:", arr); // [4,5] , []
    console.log("splice后:", arr.splice(0, 3), "原数组:", arr); // [] , []
    
    // Array.slice是纯函数,因为它没有副作用,对于固定的输入,输出总是固定的
    // Array.splice是不纯的,它有副作用,对于固定的输入,输出是不固定的
    
    // 结论:数组的方法本身就是函数,如果对原数组产生的修改,他就是不纯的,反之没有对原数组进行改变,就是纯函数。
    为什么函数式编程会排斥不纯的函数呢?
    再看下面的例子:
    /* 不纯的;checkage这个函数不仅取决于入参age,还取决于外部变量min,换句话说这个函数的行为需要外部的系统环境决定。
         对于大型系统来说,这种对于外部状态依赖是造成系统复杂性大大提高的主要原因 */
    var min = 18;
    var checkage = (age) => age > min;
    
    // 纯的:checkage函数把关键数字18硬编码到函数的内部,扩展性差,耦合性大,我们可以用柯里化解决这个问题
    var checkage = (age) => age > 18;
    二、柯里化函数
    // 以下就是柯里化实现:我们把age固定住,然后把入参18传入,这样既做到了解耦,又不会创建中间变量,变成了纯函数
    //  纯函数顾名思义,就是只有函数,没有中间变量,这是我不严谨的总结 ^_^
    // 柯里化函数:组织入参
    function curryFn(fn, ...args) {
      return (..._args) => {
        return fn.call(null, ...args, ..._args);
      };
    }
    // 判断方法
    function judgeFn(age, min) {
      return age > min;
    }
    
    var tempFn = curryFn(judgeFn, 20);
    var res = tempFn(18);
    console.log(res, "柯里化函数"); // true
    柯里化的意义:
    事实上柯里化是一种“预加载”函数的方法,通过传递较少的参数,得到一个已经记住了这些参数的新函数,某种意义上讲,这是对参数的“缓存”,是一种非常高效的编写函数的方法。
    三、函数组合
    // 看这个函数嵌套: h(g(f(x))); 怎么形成这样的“包菜式”代码呢?以下就是包菜式代码的拆解:函数执行时返回另一个函数,再执行又返回另一函数,一层层的
    function h(value) {
      // console.log(value, 'h函数中收到的value');  // 5
      return value;
    }
    function g(value) {
      // console.log(value, 'g函数中收到的value');  //5
      return value;
    }
    function f(x) {
      return x;
    }
    h(g(f(5))); // 输出5
    // 虽然这也是函数式的代码,但它依然存在某种意义上不的“不优雅”。为了解决函数嵌套的问题,我们需要用到“函数组合”
    // 请看下面的例子(1):
    
    function g(val) {
      // console.log(val, 'g函数中val的值');  // 5
      return val;
    }
    function f(x) {
      return x;
    }
    
    var compose = function (g, f) {
      return function (x) {
        return g(f(x));
      };
    };
    // ES6写法:
    var compose = (g, f) => (x) => g(f(x));
    compose(f, g)(5); // 5
    // 例子2 :内层f(x)的返回值作为g函数的入参执行g函数中的逻辑并返回
    var g = (val) => (val += 100);
    var f = (x) => x * x;
    var compose = (g, f) => (x) => g(f(x));
    compose(g, f)(5); // 125
    总结:
    我们定义的compose就像双面胶一样,可以把任何两个纯函数粘合在一起。当然你也可以扩展出组合三个函数的“三面胶”,甚至"N面胶"
    这种灵活的组合可以让我们像拼积木一样来组合函数式代码:
    例:
    var lastEle = (arr) => arr[arr.length - 1];
    var reverse = (arr) => arr.reverse();
    
    var compose = (f1, f2) => (arr) => f1(f2(arr));
    compose(lastEle, reverse)([1, 2, 3, 4, 5]); // 1  ->先执行reverse函数把数组反序,然后把反序后的数组作为入参传给lastEle函数取最后一个元素
    四、Point Free : 不要转瞬即逝的中间变量
    有了柯里化和函数组合的基础知识,下面介绍一下Point Free这种代码风格
    /*
     下面代码分析:
      我们定义了一个变量f用于接收一个函数,并且函数中传入一个入参str,函数内部对str处理后并返回。
      乍一看好像平常我们也都这么写代码。
      但是这个str就是一个中间变量,但这个中间变量除了让代码变长一点以外是毫无意义的
    */
    var f = (str) => {
      return str.toUpperCase().split(" ");
    };
    f("abcd efgh"); //['ABCD', 'EFGH']
    
    // 我们对以上代码进行改造
    /* 1. 我们把上面要用到字符串方法split和toUpperCase转换成纯函数
       2. 运用函数组合compose 
    */
    var split = (x) => (str) => str.split(x);
    var toUpperCase = (str) => str.toUpperCase();
    var compose = (f, g) => (x) => f(g(x));
    
    var ff = compose(split(" "), toUpperCase);
    ff("abcd efgh"); // ['ABCD', 'EFGH']
    
    /* 综上两种写法:我们发现在最后执行的时候都是f("abcd rhgf") 
       但是第一种就会有一个str的参数传入,第二种通过函数组合,直接就把俩方法传入即可,并没有像第一种带来了中间变量str
    */
    五、命令式与声明式代码
    /* 命令式代码:
       我们通过编写一条又一条的指令去让计算机执行一些动作,这其中一般都会涉及到许多繁杂的细节。
    */
    var arr = [];
    for (let i = 0; i < 100; i++) {
      arr.push(i);
    }
    
    /* 声明式代码:
       我们通过写表达式的方式来声明我们想干什么,而不是通过一步步的指示。
    */
    var ary = [
      { name: "zs", age: "18" },
      { name: "ls", age: "19" },
      { name: "ww", age: "20" },
    ];
    var names = ary.map((x) => x.name);
    // 综上:命令式代码是展示的过程,告诉我们怎么做;声明式的代码告诉我们做什么,是结果导向
    // 函数式编程的一个明显好处就是这种声明式的代码,对于这种无副作用的纯函数,我们完全可以不考虑函数内部是如何实现的,专注于编写业务代码。优化代码时,目光只需要集中在这些稳定坚固的函数内部即可。
    // 相反,不纯的不函数式的代码会产生副作用或者依赖外部系统环境,使用他们的时候总是要考虑这些不干净的副作用。在复杂的系统中,这对于程序员的精力来说是极大的负担。
     
     
     
  • 相关阅读:
    [Kotlin] Open Classes and Inheritance
    [Kotlin] Class
    [Kotlin] Reverse a List with downTo
    [Kotlin] Named loop
    [Kotlin] for loop
    [Kotlin] Array List ArrayList
    深度解读 java 线程池设计思想及源码实现
    源码实战 | 从线程池理论聊聊为什么要看源码
    自己实现一个简单的线程池
    死磕 java线程系列之自己动手写一个线程池(续)
  • 原文地址:https://www.cnblogs.com/haoqiyouyu/p/16162679.html
Copyright © 2020-2023  润新知