一些函数式编程的思考🤔

let’s talk about functional programming

原本我以为自己对于函数式编程是有一定的认知的,或者说自己对于这个概念的掌控是足够的,然而前段时间做的一个需求中遇到了大量的前辈大佬的“真函数式编程代码”还是被教育重新做人了🙄️于是特地从家中翻出从二手市场上淘来的《javaScriptES6函数式编程入门经典》细细研究起来,有所收获记录于此📝

函数的第一条原则是要小,函数的第二条原则是要更小 –鲁迅

什么是函数式编程,为何它重要

首先看一个数学中的函数

f(x) = y;

该语句可以解读为: ‘一个函数f,以x作为参数,并返回输出y’

该定义包含了以下几个关键点:

  • 函数总是接受一个参数
  • 函数总是返回一个值
  • 函数应该根据接受到的参数而不是外部环境运行
  • 对于一个给定的x只会输出一个唯一的y

函数式编程技术主要就是基于数学函数和它的思想

来看一个简单的计税函数

let percentValue = 5;
let calculateTax = (value) => {
    return value/100 * (100 + percentValue)
}                    

这个函数准确的实现了我们的想法,但是从数学的定义上分析,就会发现calculateTax依赖了全局变量percentValue,因此在数学上它不能被称之为一个真正的函数。修复方法很简单,我们只需要移动percentValue,把它作为函数的参数。

let calculateTax = (value, percentValue) => {
    return value/100 * (100 + percentValue)
}

现在calculateTax函数可以被称为一个真正的函数了。

移除一个函数内部对全局变量的访问会使得该函数的测试更加容易

函数式编程是一种范式,我们能够以此创建仅依赖输入就可以完成自身逻辑的函数。这保证了当函数被多次调用时仍然返回相同的结果。函数不会改变任何外部环境的变量,这将产生可缓存的、可测试的代码库。

引用透明性

所有的函数对于相同的输入都将返回相同的值,函数的这一特性被称为引用透明性

看一个例子

let identity = (i) => {return i}

定义一个简单的函数identity,无论传入什么作为输入,该函数都会把它返回,在函数内部没有全局引用,我们的函数只根据传入的参数i进行操作,该函数满足引用透明性条件。

sum(4,5) + identity(1)

根据引用透明性的定义,可以把上面的语句转换为:

sum(4,5) + 1

该过程称为替换模型因为我们可以直接替换函数的结果(因为函数的逻辑不依赖其他全局变量)这一特性使得并发代码和缓存成为可能。

并发

引用透明性使得我们可以轻松地采用多线程来运行代码,根本不需要同步。为什么呢?因为同步的问题在于线程在并发运行的的时候不应该依赖全局变量。而遵循引用透明性的函数只依赖来自参数的输入,不会对全局产生任何依赖,这里没有任何“锁”🔒的概念,所以线程可以自由的运行(虽然js引擎是单线程…)

缓存

由于函数会为给定饿输入返回相同的值,实际上我们就可以缓存它。假设函数factorial用来计算给定数组的阶乘,其接受输入作为参数以计算其阶乘,5的阶乘是120,如果用户第二次用5来调用factorial,如果factorial遵循引用透明性,我们就可以直接返回缓存的值120而不用再重新计算一遍。

命令式、函数式、抽象

函数式编程主张声明式编程和编写抽象的代码

以遍历一个数组为例子:命令式编程方法遍历如下:

let array = [1,2,3]
for (i=0;i<array.length;i++){
    console.log(array[i])
}

采用声明式编程方法遍历:

let array = [1,2,3]
array.forEach((element) => console.log(element))

理解体会:移除“如何”做的部分,使用一个函数来处理“如何”做的部分,将关注点放在手头的问题(做什么上)