首发于前端361
函数式编程及其在前端的应用

函数式编程及其在前端的应用

函数式编程近几年在前端开发中非常火,那么到底什么是函数式编程?这种模式相比于其他模式又有什么优势呢?本文从五个方面入手,探讨一下函数式编程,从数学思维上解释为什么要用纯函数,要有数据不可变性,要控制副作用。

概述:

  • 函数式编程起源和概念
  • 函数式编程的特性
  • 函子的简单介绍
  • 函数式编程的优势
  • 函数式编程在前端的应用

一、函数式编程的起源和概念

1,起源

函数是数学中非常重要的概念,程序中所用到的函数和数学也是分不开的。函数式编程的起源,是一门叫做范畴论(Category Theory)的数学分支。

1.1范畴论

范畴就是使用箭头连接的物体,也就是有关联的物体。

范畴论是一门数学学科,范畴是指彼此之间存在某种关系的概念、事物、对象等等,范畴成员之间称之为态射。

数学模型

范畴论是集合论更上层的抽象,简单的理解就是"集合 + 函数"。

范畴与容器

可以把"范畴"想象成是一个容器,容器中有两样东西,一个是值,一个是变形关系函数

1.2 范畴论与函数式编程的关系

范畴中使用函数表达成员之间的关系,随着这门学科的发展,就产生了一整套函数的运算方法,在计算机硬件发展到一定阶段时,这套理论在计算机中得以实现,逐步变成了今天的函数式编程

本质上,函数式编程只是范畴论的运算方法,跟数理逻辑、微积分、行列式是同一类东西,都是数学方法,只是碰巧它能用来写程序。

1.3 数学到编程的限制

由于函数式编程从本质上讲是数学运算,原始目的是求值,不做其他事情,这也就是函数式编程为什么要求使用纯函数来编写。否则,就违背了函数最初的运算法则。

2,概念

维基百科定义:

函数式编程(英语:functional programming),又称泛函编程,是一种编程范式,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象

它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用

2.1 前端中函数式编程的定义

在前端开发中,主要开发语言是javascript,而js自身并不是函数式编程语言,也就没有一个明确的定义。

在前端开发中,通常使用FP 通过声明纯函数抽象数据的处理,来避免或尽可能减少函数调用对于外部状态和系统产生的副作用,这样一种思想称为函数式编程

二、函数式编程的特性

1,函数是'一等公民'

函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值

2,闭包和高阶函数

函数编程支持函数作为第一类对象,有时称为闭包或者仿函数(functor)对象。实质上,就是用闭包来替代对象,存储变量。

由于函数是一等公民,函数也可以当作参数传递,就形成了高阶函数。

高阶函数可以用另一个函数(间接地,用一个表达式) 作为其输入参数,在某些情况下,它甚至返回一个函数作为其输出参数。

3,惰性计算

在惰性计算中,表达式不是在绑定到变量时立即计算,而是在求值程序需要产生表达式的值时进行计算

4,声明式(表达式)

声明式代码:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。

函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。

5,纯函数

输入一定时,输出一定):输出不受外部环境影响,同时也不影响外部环境,无副作用

immutable

纯函数的变量也不是命令式编程语言中的变量,即存储状态的单元,而是代数中的变量,即一个值的名称。变量的值是不可变的,也就是说不能多次赋值。

6,尾递归和尾调用

尾调用是函数式编程中一个很重要的概念,当一个函数执行时的最后一个步骤是返回另一个函数的调用,这就叫做尾调用。

尾递归是指在函数最后调用函数自身

7,函数柯里化

  • 将多参数的函数转换成单参数的形式
  • 将一个低阶函数转化为高阶函数的过程

8,函数compose

将多个函数合并成一个函数,并且和顺序无关

9,Point free

函数无须提及将要操作的数据是什么样的

这种风格能够帮助减少不必要的命名,让代码保持简洁和通用。

三、函子的简单介绍

1,函子的概念

前面介绍了范畴,将两个范畴关联起来的数据类型成为函子

函子也是基本的运算单位和功能单位。

2,函子的数据结构

任何具有map方法的数据结构,都可以当作函子的实现。

例子:

函子的of方法

函数式编程一般约定,函子有一个of方法,用来生成新的容器。

3,前端中函子的应用举例

例子

深入访问object的属性的时候,不会担心由于属性不存在、undefined、null等问题出现异常。

四、函数式编程的优势

1. 代码简洁,开发较快

函数式编程大量使用函数,功能抽离,减少了代码的重复,代码复用性高,因此程序比较短,开发速度较快。

2. 易于理解,接近自然语言

用描述性的表达式组合不同的函数形成程序,易于理解

比如:(1 + 2) * 3 - 4 <-------> subtract(multiply(add(1,2), 3), 4)

3. 易于代码的维护和管理

函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。

4. 易于"并发编程"

函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"(concurrency)

5. 易于代码升级和扩展

函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。

五、函数式编程在前端的应用

1,框架层面的应用

1.1 React函数式编程

函数式编程时react框架设计的核心思想之一,每个组件其实都是一个函数,对这些函数进行组合之后形成data和view之间的映射关系,React自身可以看作是一个ui函数,入参是data

例子:

dom函数化(jsx),react算法中的尾递归,尾调用,fiber生成算法,hooks等

1.2 vue3函数式编程

vue3新增了一个新的函数setup,类似于react的hooks

1.3 Redux函数式编程

redux是函数式编程在解决前端数据流中很典型的应用。

中间件机制(compose),纯函数Reducer,createStore(enhancer)等

1.4 Rxjs函数式编程

Rxjs是一个解决复杂异步操作的库,结合了响应式编程和函数式编程。

1.5 koa中的函数式编程

中间件的实现compose

1.6 lodash中的函数式编程

lodash中的fp模块

还有很多框架有函数式编程的应用,这里不一一列举。

2,在单元测试中的应用

严格函数式编程的每一个符号都是对直接量或者表达式结果的引用,没有函数产生副作用。对被测试程序中的每个函数,只需在意其参数,而不必考虑函数调用顺序,不用谨慎地设置外部状态。所有要做的就是传递代表了边际情况的参数。如果程序中的每个函数都通过了单元测试,软件系统的质量就有了保证

3,在es6之后的应用

  • 箭头函数
  • 数组的扩展 find findIndex
  • Map数据结构
  • Reflect映射
  • 对象的链判断运算符(MayBe函子) message?.body?.user?.firstName
  • 函数扩展 尾调用优化
  • 最新提案:函数的部分执行
  • gua管道运算符 >|

...

4,允许可控制的副作用共存

  • 前端可能的副作用
  • 更改全局状态
  • 发送一个 http 请求
  • 可变数据
  • 打印/log
  • 获取用户输入
  • DOM 查询
  • 访问系统状态
  • 在无法避免副作用的情况下,使用函子和单子来控制副作用,也包括错误的处理。
  • 许多异步解决方案其实是函子的具体实现,比如Promise是一个Monad

总结

1,前端函数式编程的现状

  • 函数式编程的核心是引用透明,结果可预测,数据不可变,在编程过程中带来了易于维护,并行编程的开发体验,具有很重要的意义。
  • 函数式编程为组件的编写提供了更高的灵活度与可读性,并且更符合一个前端编写者的习惯,目前在前端应用非常广泛。尤其以react, redux应用最为流行和广泛,以及其他衍生生态。
  • JavaScript,作为最流行的编程语言之一,特别适合函数式编程

2,函数式编程趋势和未来展望

  • cpu和内存性能的提高
  • 函数式编程正逐渐成为计算机科学和软件工程领域的流行趋势
  • 函数式编程发展的应该是思想,而不是以函数式编程为基础语言的发展

书籍推荐


参考:

编辑于 2020-09-03 16:10