纯函数的记录-函数式编程

78 阅读6分钟

前言

这一期是看到了关于纯函数相关的知识,所以来进行一个简单的记录以保证之后看到之后有大致的印象

纯函数是什么?

维基百科对于纯函数是这样解释的 image.png 那么抛开维基百科的解释从函数式编程的角度看,我们可以把满足以下两项条件的函数称为纯函数:
1、对于相同的输入值,都应该得到相同的输出值。函数的输出和输入值以外的其他隐藏信息或无关,也和由I/O设备产生的外部输出无关
2、在执行过程中没有语义上可观察的副作用。诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。

对于相同的输入值,都应该得到相同的输出值

纯函数的输出可以不用和所有的输入值有关,甚至可以和所有的输入值都无关。但纯函数的输出不能和输入值以外的任何资讯有关。纯函数可以传回多个输出值,但上述的原则需针对所有输出值都要成立。
什么是输入值?你可以简单的理解为函数的传参。我们可以举个例子:

var a = 10, b = 20
function getMath(){
  return a * b 
}

上面这一段函数就是典型的非纯函数。在函数内部使用了ab两个属性,而这两个属性并不是作为参数而是通过全局环境(这是隐式数据交换,与维基百科的显示不符)拉进来的。
函数的输出值跟输入值(因为没有显式输入,所以是void)没有任何关联,这就违反了标题所声明的对于相同的输入值,都应该得到相同的输出值
我们可以改造一下上面的代码,只需要将a,b作为参数传入getMath()函数中就好

var a = 10, b = 2
function getMath(n1,n2){
  return n1 * n2
}
getMath(a,b) // 20
a = 100
b = 4
getMath(a,b) // 400

这样一来,函数就可以达成对于相同的输入值(参数),都应该得到相同的输出值

在执行过程中没有语义上可观察的副作用

在维基百科中是这样解释函数副作用的:
image.png 或许你不太理解?没关系!我们可以使用日常生活中的例子来辅助理解:
在日常生活中我们通常会使用药物来治疗一些疾病,在药物的说明书上会有成分表、主治症状、禁忌等等,还有一项就是:副作用。这对我个人来说非常重要,因为副作用决定着我是否要服用该药物。
虽然此副作用彼副作用,但是两者都有一点共通性,简单的讲就是在预期结果之外的,对外部环境所造成的影响。就好比说使用奥美拉唑肠溶片来治疗反流性食管炎(反流性食管实在让人烦躁),虽然可能效果比较显著但是同时会带来腹痛、恶心、呕吐等等一系列超出预期的副作用。
那么从药品推论到函数式编程中, 我们可以知道除了正常的计算结果之外,不会对外部环境以及执行上下文产生影响就叫做在执行过程中没有语义上可观察的副作用

即使只是console.log()

副作用包括对外部状态的修改、网络请求、数据库操作等。即使我们只是在函数内部console.log()打印某个参数,同样的,我们用一段简单的代码来说明一下

function getFullLabel(label,code){
  console.log(label,code)
  return `${label}(${code})`
}

上面的代码中,我们简单的拼接了一下labelcode,这两个都是被作为参数传进函数中的。
如果只按照相同输入得相同输出来说可能是一个纯函数,但是代码中执行了console.log()向控制台输出打印labelcode,执行console.log()后出现了一条数据外流链导致getfullLabel变成了一个非纯函数

网络请求也不行

同样的情况还有网络请求,因为网络请求通过与外部资源进行交互,而在这一过程中可能会收到诸如请求成功请求失败网络延迟等等不同的结果。
所以一个纯函数内部无法直接构建网络请求。首先网络请求无法保证相同的输入得到相同的输出,其次还可能会得到不同的数据影响到函数外部环境。

// 不纯的函数,引入了网络请求
function fetchData(url) {
  // 发起网络请求,获取数据
  // ...
  return data;
}
// 纯函数,接受数据作为参数进行处理
function processData(data) {
  // 处理数据
  // ...
  return result;
}
const data = fetchData('https://api.example.com/data');
//我们可以通过把请求结果抛给processData处理,这样可以保证processData是一个纯函数
const result = processData(data); 

除此之外,还有修改/读取配置文件在函数内修改传入参数等等也可以看做是一种副作用,也就是非纯函数

纯函数的优点

那么纯函数到底有什么优点?或者说它能帮我们解决什么问题?因为纯函数的特性,我们可以知道它在某些方面有独特的优越性,比如说:
1、测试性: 由于纯函数的输出只依赖于输入,不依赖于外部状态或副作用,因此可以更容易地编写单元测试。我们可以通过提供不同的输入来验证函数的输出,不需要担心对外部状态的影响。
2、组合型: 纯函数具有良好的组合型。我们可以将纯函数组合为更加复杂的函数,同时不用担心他们之间的相互影响,这样可以使代码更加模块化

function toUpperCase(str) {
  return str.toUpperCase();
}
function removeSpaces(str) {
  return str.replace(/\s/g, '');
}
function reverseString(str) {
  return str.split('').reverse().join('');
}
// 创建一个函数调用链
const result = reverseString(removeSpaces(toUpperCase(' Hello, World! ')));
console.log(result); // 输出: "!DLROW,OLLEH"

3、缓存性: 应用程序或者运行环境(Runtime)可以对纯函数的运算结果进行缓存。由于纯函数的输出仅由输入决定,相同的输入将始终产生相同的输出。这使得纯函数非常适合缓存结果,以避免重复计算。通过缓存函数的输出,可以提高性能并减少计算开销
4、线程安全: 由于纯函数没有副作用,所以可以保证在并行运行时是安全的。这意味着你可以在多个线程或进程中同时调用纯函数,而无需担心竞态条件或数据争用问题。
5、移植性: 纯函数不依赖于外部状态或特定的上下文环境。你可以随意的将编写好的纯函数移植到其他项目中复用,不用担心在新的环境出现问题

结尾

纯函数是函数式编程的一个最大的前提,也是这个知识体系的根基
那么经过上面的学习,也可以知道纯函数的特性如何编写一个纯函数以及纯函数的优点,希望可以在之后的编程中尽可能的做到遵循纯函数的特性。
谢谢大家!