- 为什么要学习函数式编程
- React的流行
- Vue3也开始拥抱函数式编程
- 函数式编程可以抛弃this
- 打包过程中可以更好的利用tree shaking过滤无用代码
- 方便测试、方便并行处理
- 很多框架库:lodash、underscore、ramda
- 什么是函数式编程
函数式编程(Functional Programming, FP),FP 是编程范式之一,我们常听说的编程范式还有面向过程编程、面向对象编程。- 面向对象编程的思维方式:把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和多态来演示事物事件的联系
- 函数式编程的思维方式:把现实世界的事物和事物之间的联系抽象到程序世界(对运算过程进行抽象)
- 程序的本质:根据输入通过某种运算获得相应的输出,程序开发过程中会涉及很多输入和输出的函数
- x -> f(联系、映射) -> y,y=f(x)
- 函数式编程中的函数指的不是程序中的函数(方法),而是数学中的函数即映射关系,例如:y= sin(x),x和y的关系
- 相同的输入始终要得到相同的输出(纯函数)
- 函数式编程用来描述数据(函数)之间的映射
- 前置知识
- 函数是一等公民
- 函数可以存储在变量中
- 函数作为参数
- 函数作为返回值
- 高阶函数(Higher-order function)
- 可以把函数作为参数传递给另一个函数
- 可以把函数作为另一个函数的返回结果
- 意义:抽象可以帮我们屏蔽细节,只需关注与我们的目标;高阶函数式用来抽象通用的问题
- 闭包(Closure)
- 函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包。
- 可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员
- 闭包的本质:函数在执行的时候会放到一个执行栈上当函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员
- 函数是一等公民
- 纯函数
- 概念:相同的输入永远会得到相同的输出,而且没有任何可观察的副作用
- 纯函数的好处
- 可缓存
因为纯函数对相同的输入始终有相同的结果,所以可以把纯函数的结果缓存起来 。(memoize) - 可测试
纯函数让测试更方便 - 并行处理
在多线程环境下并行操作共享的内存数据很可能会出现意外情况 。纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数 (Web Worker)
- 可缓存
- 副作用:没有任何可观察的副作用。副作用来源:配置文件、数据库、获取用户输入等
- 柯里化(HaskellBrooks Curry)
当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后永远不变)然后返回一个新的函数接收剩余的参数,返回结果可解决硬编码问题- lodash中的柯里化函数(_.curry(func))
- 功能:创建一个函数,该函数接收一个或多个func的参数,如何func所需要的参数都被提供则执行func并返回执行的结果。否则继续返回该函数并等待接收剩余的参数
- 参数:需要柯里化的函数
- 输出:柯里化后的函数
- 总结
- 柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数
- 这是一种对函数参数的’缓存’
- 让函数变的更灵活,让函数的粒度更小
- 可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能
- lodash中的柯里化函数(_.curry(func))
- 函数组合
纯函数和柯里化很容易写出洋葱代码函数组合可以让我们把细粒度的函数重新组合生成一个新的函数- 函数组合(compose)
- 如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个函数
- 函数就像是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终结果
- 函数组合默认是从右到左执行
- 调试
- 如何调试组合函数(柯里化trace函数)
- lodash/fp
提供了实用的对函数式编程友好的方法提供了不可变 auto-curried iteratee-fifirst data-last 的方法
- Point Free
我们可以把数据处理的过程定义成与数据无关的合成运算,不需要用到代表数据的那个参数,只要把简单的运算步骤合成到一起,在使用这种模式之前我们需要定义一些辅助的基本运算函数。- 不需要指明处理的数据
- 只需要合成运算过程
- 需要定义一些辅助的基本运算函数
- 函数组合(compose)
- Functor(函子)
函数式编程中如何把副作用控制在可控的范围内、异常处理、异步操作等。
- 什么是Functor
- 容器:包含值和值的变形关系(这个变形关系就是函数)
- 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)
- 总结
- 函数式编程的运算不直接操作值,而是由函子完成
- 函子就是一个实现了map契约的对象
- 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
- 想要处理盒子中的值,我们需要给盒子的map方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理
- 最终map方法返回一个包含新值的盒子(函子)
- MayBe函子
- 我们在编程的过程中可能会遇到很多错误,需要对这些错误做相应的处理
- MayBe 函子的作用就是可以对外部的空值情况做处理(控制副作用在允许的范围)
- MayBe函子中,我们很难确认是哪一步产生的空置问题
- Either函子
- Either 两者中的任何一个,类似于 if…else…的处理
- 异常会让函数变的不纯,Either 函子可以用来做异常处理
- Either 用来处理异常
- IO函子
- IO 函子中的 _value 是一个函数,这里是把函数作为值来处理
- IO 函子可以把不纯的动作存储到 _value 中,延迟执行这个不纯的操作(惰性执行),包装当前的纯操作
- IO 函子中的 _value 是一个函数,这里是把函数作为值来处理
- 把不纯的操作交给调用者来处理
- Task异步执行
- folktale 一个标准的函数式编程库
- Pointed 函子
- Pointed 函子是实现了 of 静态方法的函子
- of 方法是为了避免使用 new 来创建对象,更深层的含义是 of 方法用来把值放到上下文Context(把值放到容器中,使用 map 来处理值)
- Monad(单子)
- 一个函子如果具有 join 和 of 两个方法并遵守一些定律就是一个 Monad
- 解决函子嵌套问题,合并返回函子的函数->flatMap