函数式编程

为什么要学,有什么好处?

  • 函数式编程随着react的流行越来越受关注
  • vue3 开始拥抱函数式编程
  • 可以抛弃this
  • 打包可以更好的利用tree shaking
  • 方便测试,方便并行处理

什么是函数式编程

编程范式( 函数式编程 , 面向对象编程 , 面向过程编程 )之一

函数式编程用来描述数据之间的映射(不是程序中的函数(方法), 而是数学中的函数和映射关系)

函数是一等公民

  • 可以存储在变量中
  • 可以作为参数
  • 可以作为返回值

高阶函数

  • 可以把函数作为参数传递给另一个函数

  • 可以把函数作为另一个函数的返回值

    使用高阶函数的意义

  • 可以帮助我们抽离通用的问题
  • 屏蔽实现功能的细节,我们只需要关注目标

闭包

  • 有权 访问 另一个函数作用域中 变量函数
  • 简单理解就是: 如果这个函数作用域可以访问另一个函数内部的局部变量,那就产生了闭包,另外那个作用域所在的函数称之为 闭包函数

闭包的本质: 函数在执行的时候会放在一个执行栈上,当函数执行完会被移除,但是因为作用域上的变量被引用所以不会被移除,因此内部函数依然可以访问外部外部函数的成员

纯函数

  • 相同的输入始终会得到相同的输出, 而且没有任何观察的副作用
  • loadsh是一个纯函数的功能库
  • 函数式编程不会保留计算中间的结果,所以变量是不可变的(无状态的)
  • 我们可以把一个函数的执行结果交给另一个函数去处理
  1. // 纯函数和不纯函数
  2. // slice/splice
  3. // splice会改变原数组

纯函数的好处

  • 可缓存

    • 因为纯函数对相同的输入总会有相同的输出,所以可以把结果缓存起来
  • 可测试
  • 可并行处理

    • 多线程环境下并行操作共享的内存数据很可能会出现意外
    • 纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数

副作用

副作用可以使一个纯函数变得不纯.如果函数依赖外部的状态,就无法保证输出相同,就会带来副作用

来源

  • 配置文件
  • 获取用户的输入
  • 数据库
  • ……

所有的外部交互都有可能带来副作用,会使得方法通用型下降,延展性下降,重用性下降.但副作用不可能完全禁止,尽可能控制在可控范围内发生

柯里化

  • 概念

    • 当一个函数有多个参数的时候先传递一部分参数调用他(这一部分参数是不变的)
    • 然后返回一个新函数接收剩余参数
  • 总结

    • 柯里化可以让我们可以得到一个已经记住部分参数的新函数
    • 可以实现部分参数的缓存
    • 可以让函数的颗粒度更小,让函数变得更灵活
    • 多个一元函数可以组合成多元函数,使功能更强大

函数组合

为什么

  • 柯里化和纯函数很容易写出洋葱代码 g(h(f(x)))
  • 函数组合可以让我们把细粒度的函数重新组合成一个新函数

概念

  • 函数组合: 如果一个函数要经过多个函数处理才能得到最终值,那么我们可以把中间过程函数合并成一个函数

    • 函数就是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终的结果
    • 函数组合默认是从右往左执行的
  • loadsh中的组合函数 flow() / flowRight()

  • flow()是从左到右的, flowRight()是从右到左的.

  • 函数的组合要满足结合律

    1. com(com(f, g), h) ==com(f, com(g, h))
    2. // => true

调试

  1. const_=require('lodash')
  2. const trace=_.curry((tag, v) => {
  3. console.log(tag, v)
  4. return v
  5. })
  6. const split=_.curry((sep, str) =>_.split(str, sep));
  7. const join=_.curry((sep, array) =>_.join(array, sep));
  8. const map=_.curry((fn, array) =>_.map(array, fn));
  9. const f=_.flowRight(join('-'),
  10. trace('map 之后'),
  11. map(_.toLower),
  12. trace('split 之后'),
  13. split(' '))
  14. console.log(f('NEVER SAY DIE'))
  15. // 利用柯里化简化参数,接收参数并打印返回,达到调试的目的

Point Free

  • 我们把数据处理的过程定义成与数据无关的合成运算.不需要用到代表数据的那个参数,只要把运算步骤合成到一起,在使用之前我们需要定义一些简单的辅助函数.

    • 不需要指明处理的数据
    • 只需要合成运算过程
    • 需要定义一些辅助的基本运算函数

函子

学习目的

  • 为了在函数式编程中把副作用控制在可控的范围内,异常处理,异常操作等

是什么

  • 容器: 包含值与值的变形关系(函数)
  • 函子: 是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)

函子总结

  • 函数式编程不直接操作值,而是由函数来完成

  • 函子就是一个实现map的对象

  • 可以把函子想象成一个盒子,里面封装了一个值

  • 想要处理这个值,我们就要给盒子的map方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理

  • 最终map方法返回一个包含新值的盒子(函子)

  • MayBe 函子

    • 可以处理外部的空值情况
  • Either 函子

    • 类似if else 的处理.处理异常
  • IO 函子

    • IO函子中的_value是一个函数,把函数当成值来处理
    • IO函子可以把不纯的动作存储到_value中, 延迟执行这个不纯的操作(惰性),
    • 把不纯的操作交给调用者来处理
  • Task 异步执行

  • Pointed 函子

  • Monad(单子)