追求“纯”的纯函数 | 函数式编程

2,202 阅读4分钟

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

关于 函数式编程 整理的一些笔记,都统一记录在👉 函数式编程专栏

	graph LR
    纯函数--> A(纯函数的概念)
    纯函数--> B(如何理解'无可观察的副作用')
    纯函数--> C(纯函数的好处)

纯函数的概念

在程序设计中,如果一个函数符合以下条件,则被称之为纯函数:

  1. 相同的输入,产生相同的输出
  2. 无可观察的副作用
  3. 不依赖于外部状态

例1 - 不纯的函数

let a = 1;
function sum(b){
  return a+b
}
console.log(sum(2)) // 3

例1中,违反了规则1和规则3,相同的输入没有产生相同的输出,变量a的变动随时都会影响到内部的计算结果。

例2 - 纯函数

function sum(a,b){
  return a+b
}

console.log(sum(1,2)) // 3

例2是个纯函数,相同的输入有相同的输出,也没有依赖于外部变量,更没有可观察到的副作用。

条件1 和 条件3 很好理解,关于纯函数的第2个条件-"无可观察的副作用"如何理解呢?

如何理解"无可观察的副作用"

什么是副作用

  • “作用”是一切除结果计算之外发生的事情,它本身并没有坏处。往往 “一只蚂蚁搅乱一锅粥”,正是因为“副”作用中“副”的存在,导致衍生出奇奇怪怪的问题。
  • 总结:副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。

副作用来源

只要是跟函数外部环境发生交互的都是副作用

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

所有的外部交互都有可能带来副作用,副作用也使得方法通用性下降不适合扩展和可重用性,同时副作用会给程序中带来安全隐患给程序带来不确定性,但是副作用不可能完全禁止,我们不能禁止用户输入用户名和密码,只能尽可能控制它们在可控范围内发生。

纯函数的好处

可缓存

因为对于相同的输入始终有相同的结果,那么可以把纯函数的结果缓存起来,可以提高性能。

举个🌰:计算一个数值绝对值的纯函数

// 计算一个数的绝对值
function getABS(x) {
  console.log(x);
  return Math.abs(x)
}
console.log(getABS(-6)); // 6
console.log(getABS(-6)); // 6
console.log(getABS(-6)); // 6

在上面的例子中,我们看到最终的打印结果是:三个6。 结果没有缓存处理,所以getABS()函数内部会打印三次x

-6
-6
-6

接下来,我们写一个记忆函数(模拟Lodash中的memoize) 这里做了简化版,创建一个会缓存 func 结果的函数, 如果缓存中有值就把值返回, 没有值就调用func函数并且把参数传递给它

// 一个简易版本的记忆函数
function memoize (func) {
  if (typeof func != 'function') {
    throw new TypeError('Expected a function');
  }
  var memoized = function() {
    var args = arguments,
        key = args[0],
        cache = memoized.cache;
    // 如果缓存中有值就把值返回
    if (cache.has(key)) {
      return cache.get(key);
    }
    // 没有值就调用func函数并且把参数传递给它
    var result = func.apply(this, args);
    memoized.cache = cache.set(key, result) || cache;
    return result;
  };
  memoized.cache = new Map()
  return memoized;
}

const m = memoize(getABS)

console.log(m(-6));  // 6
console.log(m(-6));  // 6
console.log(m(-6));  // 6

最终的打印结果,和上面(没缓存过)的一样。 区别在于,函数内部经过缓存,只会打印一次 x

-6

可测试

  • 纯函数让测试更加容易。
  • 不需要伪造一个“真实的”支付网关,或者每一次测试之前都要配置、之后都要断言状态(assert the state)。只需简单地给函数一个输入,然后断言输出就好了。

并行处理

  • 多线程环境下并行操作共享的内存数据很可能会出现意外情况。纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数
  • 虽然JS是单线程,但是ES6以后有一个Web Worker,可以开启一个新线程

附录