概述
- 介绍函数式编程的基本概念、优势以及在项目中的应用
- 目标:带领大家认识函数式编程,了解函数式编程的基本使用规则,能够在项目中应用,拓展大家思考问题的方向
- 问题:
- 论证用函数式编程的好处在哪儿
- 比封装普通函数优势在哪儿
- 对加入新功能和修改bug的提效是否有帮助
- 函数式编程 - 代码逻辑复用相关
- react中的函数式编程
函数式编程介绍
我们常见的编程范式有哪些
💡 特征,优缺点,在项目中的应用程度面向过程编程
-
Demo
const a = 1 const b = 2 const add = a + b -
优点
- 一目了然,容易理解
- 命令式编程是一种编程风格,程序员通过告诉计算机他们想要什么来告诉计算机该做什么。然后计算机必须弄清楚如何产生结果。
- 命令式编程产生了我们每天使用的许多结构:控制流(if-else)、算术运算符(+-*/)、比较运算符(><)和逻辑运算符(===、!==)
-
缺点
- 编写大型项目和复杂的逻辑时,代码会变得非常冗余,容易产生bug,且维护成本高
- 复用性差
面向对象编程
-
Demo
class Calculator{ add(a,b){ return a + b } } const c = new Calculator() c.add(1,2) -
优点
- OOP(Object Oriented Programming)
-
缺点
- 更多的代码量,对于简单的函数实现来说不够简洁
什么是函数式编程
💡 特征,优缺点,对函数式编程的印象定义
wiki
函数式编程是一种编程范式,它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。其中,λ演算是该语言最重要的基础。而且λ演算的函数可以接受函数作为输入的参数和输出的返回值。
转换为我们自己的理解
- 纯函数
- 函数可以作为参数传递
- 数据不可变
概念
函数的元
-
不论使用何种方式去构造一个函数,这个函数都有两个固定的信息可以获取:
- name 表示当前标识符指向的函数的名字。
- length 表示当前标识符指向的函数定义时的参数列表长度。
// 一元函数 x const one = a => a // 二元函数 x+y const two = (a,b) => a+b // 三元函数 x+y+z const three = (a,b,c) => a+b+c -
定义函数的元的意义在于,我们可以
对函数进行归类,并且可以明确一个函数需要的确切参数个数。函数的元在编译期(类型检查、重载)和运行时(异常处理、动态生成代码)都有重要作用。
柯里化(函数元的降维技术)
-
柯里化(Currying)是一种将接受
多个参数的函数转换成一系列接受一个参数的函数的技术。是利用closure把函数的直接调用变成延迟调用的偏函数(不完全执行) -
demo
const add = (a,b,c) => a+b+c const curryAdd = curry(add) curryAdd(1)(2)(3) -
作用:
- 更加灵活
- 更加可组合
- 更加方便调试
函子(Functor)
- 函子(Functor)是一种范畴论中的概念,它是一个封装了值(value)的
容器,并提供了一些操作来对容器内的值进行变换的抽象。函子通过一个map方法来对容器内的值进行变换并返回一个新的函子。 - 函子的特点是具有可组合性和纯函数式的性质,它们可以在不改变原有的结构的情况下对值进行操作和转换,从而方便地组合多个变换操作,达到更加复杂的操作目的。
- 一些常用的函子
-
Maybe 函子用于表示可能为空的值,它通过封装一个值来避免出现空指针异常。Maybe 函子的 map 方法会将传入的函数应用于封装的值,如果值存在,则返回变换后的值的 Maybe 函子,如果值不存在,则返回一个空的 Maybe 函子。class Maybe { constructor(val) { this.val = val; } map(f) { return this.val != null ? new Maybe(f(this.val)) : new Maybe(null); } } const maybeValue = new Maybe(5); const newValue = maybeValue.map(x => x * 2); // 返回封装了新值的 Maybe 函子 const nullValue = new Maybe(null); const nullNewValue = nullValue.map(x => x * 2); // 返回一个空的 Maybe 函子 -
Either 函子用于表示两个可能的值中的任意一个。Either 函子的实例有两种情况,一种是 Left 函子,表示错误或异常情况,另一种是 Right 函子,表示正常情况。Either 函子的 map 方法会对 Right 函子的值应用传入的函数,对 Left 函子的值不做任何操作。class Either { constructor(val) { this.val = val; } static left(val) { return new Left(val); } static right(val) { return new Right(val); } map(f) { return this.val instanceof Left ? this : Either.right(f(this.val)); } } class Left extends Either { map() { return this; } } class Right extends Either {} const eitherValue = Either.right(5); const newValue = eitherValue.map(x => x * 2); // 返回封装了新值的 Right 函子 const eitherError = Either.left("error"); const newEitherError = eitherError.map(x => x * 2); // 返回原始的 Left 函子 -
IO 函子用于封装具有副作用(如读取文件、发送请求等)的函数,使得这些函数的执行时机可以被延迟,并且可以方便地组合和重用。IO 函子的 map 方法会对函数进行包装,而不会立即执行。class IO { constructor(f) { this.f = f; } static of(x) { return new IO(() => x); } map(f) { return new IO(() => f(this.f())); } chain(f) { return f(this.f()); } } const ioValue = IO.of(5); const newValue = ioValue.map(x => x * 2); // 返回封装了新值的 IO 函子 const ioAction = new IO(() => console.log("Hello, world!")); const newIoAction = ioAction.map(x => x.toUpperCase()); // 返回封装了函数的 IO 函子
-
单子(Monad)
Functors, Applicatives, And Monads In Pictures - adit.io
pointFree(无参风格)
-
Pointfree 的本质就是使用一些
通用的函数,组合出各种复杂运算。上层运算不要直接操作数据,而是通过底层函数去处理。这就要求,将一些常用的操作封装成函数。 -
例子可以看文章
-
demo
-
作用
- 它使程序
更简单、更简洁。 - 它使算法更清晰。通过只
关注被组合的函数,我们可以更好地了解正在发生的事情,而不会受到数据参数的阻碍。 - 它迫使我们
更多地考虑正在完成的转换,而不是正在转换的数据。 - 它帮助我们将函数视为可以处理不同类型数据的
通用构建块,而不是将它们视为对特定类型数据的操作。通过给数据命名,我们可以确定我们可以在哪里使用我们的函数的想法。通过将数据参数排除在外,它使我们更具创造力。
- 它使程序
React中的函数式编程
-
函数式组件
React 中的函数式组件是纯函数的体现,通过使用纯函数的方式来构建 UI,可以更容易实现代码的可测试性和可维护性。
-
Hooks
React 的 Hooks 是一种函数式编程的方式来管理组件的状态和副作用等逻辑,通过使用 hooks,我们可以将组件中的状态和副作用等逻辑提取到一个单独的函数中,并使用函数调用的方式在组件中共享使用。这种方式更符合函数式编程的思想,避免了类组件中 this 和生命周期等特性的使用,提高了代码的可读性和可维护性。
-
数据不可变性
React 推崇的数据更新方式是不可变性,即通过创建新的对象来更新数据,而不是直接修改原始数据。这种方式避免了许多错误,提高了代码的可维护性,并且更容易实现代码的优化。
-
组合、HoC
React 中的组件是可以组合的,通过组合不同的组件,可以构建出复杂的 UI。这种方式与函数式编程中的组合思想非常相似,可以更好地实现代码的可重用性和可维护性。
在项目中的实践
-
主要还是结合ramda使用,我们可以先把ramda用好用熟练,后面再探索更高级的用法
总结
函数式编程的优缺点
优点
- 更好的
管理状态:因为它的宗旨是无状态,或者说更少的状态,能最大化的减少未知代码、减少出错情况 - 更简单的
复用:固定输入->固定输出,没有其他外部变量影响,并且无副作用。这样代码复用时,完全不需要考虑它的内部实现和外部影响 - 更优雅的
组合:往大的说,网页是由各个组件组成的。往小的说,一个函数也可能是由多个小函数组成的。更强的复用性,带来更强大的组合性 - 隐性好处。减少代码量,提高维护性
缺点
性能:函数式编程相对于指令式编程,性能绝对是一个短板,因为它往往会对一个方法进行过度包装,从而产生上下文切换的性能开销资源占用:在 JS 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收所产生的压力远远超过其他编程方式递归陷阱:在函数式编程中,为了实现迭代,通常会采用递归操作
思考
函数式编程只是万千编程范式的一种,它可以拓宽我们思考问题和编写代码的思路,它的很多设计理念值得我们借鉴和思考,但并不是解决问题的万能药剂,不光函数式编程不是,其他任何一种编程范式都不是,我们在编程时应该结合实际业务使用最合理的范式。
参考链接
Functional Components with React stateless functions and Ramda