函数式编程-纯函数

2019-10-15 13:43:4510/1/2021, 3:34:43 AM

纯函数的基本概念

纯函数是这样一种函数,相同的输入永远会得到相同的输出,而且没有任何可观察的副作用。

可能会导致不纯的情况:

  • 修改调用/传入的参数

    let array = [1, 2, 3, 4, 5];
    
    // 纯的
    array.slice(0, 3); //[1, 2, 3]
    array.slice(0, 3); //[1, 2, 3]
    
    // 不纯的 --> 会修改array
    array.splice(0, 3); //[1, 2, 3]
    array.splice(0, 3); //[4, 5]
    
  • 依赖外部环境

    // 纯的
    function checkAge(minmun, age) {
      return age > minmun;
    }
    
    // 不纯的 --> 引入了外部的环境,从而增加了认知负荷(cognitive load)
    let minmum = 21;
    function checkAge(age) {
      return age > minmun;
    }
    

副作用

副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。

副作用可能包括:

  • 更改文件系统
  • 往数据库插入记录
  • 发送一个 http 请求
  • 修改传入的数据
  • 打印/log
  • 获取用户输入
  • DOM 查询
  • 访问系统状态
  • ......

追求纯的理由

  • 可缓存性(cacheable)

    function memoize(f) {
      let cache = {};
      return function() {
        let arg_str = JSON.stringify(arguments);
        console.log(cache[arg_str]);
        cache[arg_str] = cache[arg_str] || f.apply(f, arguments);
        return cache[arg_str];
      };
    }
    
    const squareNumber = memoize(x => x * x);
    
    squareNumber(3);
    squareNumber(3);
    
  • 可移植性/自文档化(portable/self-documenting)

  • 可测试性(testable)

  • 合理性(reasonable)

    使用纯函数的最大好处是引用透明性(referential transparency)

    如果一段代码可以替换成它执行所得的结果,而且是在不改变整个程序行为的前提下替换的,那么就认为这段代码是引用透明的。由于纯函数总是能够根据相同的输入返回相同的输出,所以它们就能够保证总是返回同一个结果,这也就保证了引用透明性。对于引用透明的代码,可以使用"等式推导"(equational reasoning)的技术来分析代码,所谓"等式推导"就是"一对一"替换,有点像在不考虑程序行执行的怪异行为(quirks of programmatic evaluation)的情况下手动执行相关代码。

    let Immutable = require("immutable");
    
    // decrementHP是纯函数!!!因为player的来源是immutable.Map,set方法不会使map改变,而是返回一个新的map对象
    function decrementHP(player) {
      return player.set("hp", player.hp - 1);
    }
    
    function isSameTeam(player1, player2) {
      return player1.team === player2.team;
    }
    
    function punch(player, target) {
      if (isSameTeam(player, target)) {
        return target;
      } else {
        return decrementHP(target);
      }
    }
    
    let jobe = Immutable.Map({ name: "Jobe", hp: 20, team: "red" });
    let michael = Immutable.Map({ name: "Michael", hp: 20, team: "green" });
    
    punch(jobe, michael);
    //=> Immutable.Map({name:"Michael", hp:19, team: "green"})
    

    关于 decrementHP 是不是纯函数的讨论

  • 并行代码

    可以并行运行任意纯函数,因为纯函数根本不需要访问共享内存,而且根据其定义,纯函数也不会因副作用而进入竞态(race condition)