01 函数式编程的基本概念

什么是函数式编程?

函数式编程是一种强调以函数为主的软件开发风格。

函数式编程的目标是使用函数来抽象作用在数据之上的控制流与操作,从而在系统中消除副作用减少对状态的改变


函数式编程是指为创建不可变的程序,通过消除外部可见的副作用,来对纯函数进行声明式的求值过程。


函数式编程是通过函数将值转化成抽象单元,接着用于构建软件系统。

你要形成一种简单的思维过程,即在另一个层面来创建参数化的函数,其参数不再只是值,也可以是提供更多功能的函数。执行并组合各种函数来实现强大的功能。

要把函数理解为一个固定的法则,当一个或一堆值从函数的一端进入,经过法则的改变,变为了另外一些值。

基本原则

分解-抽象-组合

将程序分解为一些更可重用、更可靠且更易于理解的部分,再将它们组合起来,形成一个更易推理的程序整体。所有的函数式程序都遵循这一原则。

无状态和不变性

函数式编程旨在尽可能提高代码的无状态性不变性。无状态的代码不会改变或破坏全局的状态。

函数式编程基于一个前提,即使用纯函数构建具有不可变性的程序

基本概念


  • 什么是声明式编程
  • 什么是纯函数
  • 什么是引用透明性
  • 什么是不可变性

1. 声明式编程

强调程序应该做什么(函数组合),而不像命令式编程那样强调如何做(多条指令)。

声明式编程是将程序的描述与求值分离开来的。它关注于如何用各种表达式来描述程序逻辑,而不一定要指明其控制流或状态变化。

比如说对数组的操作,只需要对应用在数组每个元素上的行为予以关注,将循环交给系统的其他部分去控制。

2. 纯函数

没有副作用和状态变化的函数成为纯函数。

纯函数具有的性质:

  • 取决于提供的输入,而不依赖于任何在函数求值期间或调用间隔时可能变化的隐藏状态和外部状态
  • 不会造成超出其作用域的的变化,例如修改全局对象或者引用传递的参数。
有哪些行为算是副作用?
  • 改变一个全局变量、属性或数据结构
  • 改变一个函数参数的原始值
  • 处理用户输入
  • 抛出一个异常,除非它又被当前函数捕获了。
  • 屏幕打印或记录日志
  • 查询HTML文档、浏览器的cookie或者访问数据库
函数式编程也不是禁止一切状态的改变

在实践上,函数式编程并不限制一切状态的改变,重要的是,它只是提供了一个框架来帮助管理和减少可变的状态,同时让你能够将纯函数从不纯的部分分离出来。

实践中的函数是编程并不会避免状态改变,而是将任何已知系统中的突变的出现,尽量压缩到最小区域。

提纯的方式
  • 将一个长函数或者大逻辑,分离为多个具有单一职责的短函数。
  • 通过显式地将完成功能所需的依赖,都定义为函数参数,来减少副作用的数量。

3. 引用透明

定义一个纯函数的正确方式是保持引用透明性
衡量的尺度就是纯度,即函数参数和返回值之间映射的关系。如果确定的参数会产生同一确定的返回值,那么纯度就高。反之,纯度则越低。

如果一个函数对于相同的输入总是产生相同的输出(返回值或结果),那么这个函数就是引用透明的

如何引用透明性的函数,不仅易于测试,而且具有推理性,能很好的理解程序的运行。

可置换性

如果一个函数是符合引用透明性的纯函数,那么它在某一时刻(参数固定)产生的作用,可以被其产生的结果所替代。

也就是说,如果一段程序是由下列形式组成的的:

//一个输入的参数,通过过个函数的改变,最后得出结果
Program = [Input]  + [func1,func2,func3,...] -> Output

如果其中的func1,func2,func3是符合引用透明性的纯函数,那么,他们可以被产生的值所替代:

Program = [Input]  + [value1,value2,value3,...] -> Output

结果仍然是正确的。

4. 不可变性

不可变数据指的就是数据创建后不可更改。

基本数据类型,如js中的StringNumberBooleanNullundefinedsymbol,都是创建之后不可变的。

var abc = 1;这样的表达式中,虽然可以改变变量abc的值,那是那等于是更换了变量内存地址中存储的值,本来的数字1是不可变更的。

而引用类型的数据,则不具备不可变性。其内部发生了变化,引用仍然可以不变。因此如果不做特殊处理,则不具备不可变性。通常,应该创建新的引用类型数据,然后改变其中的值,最后代替原来的数据赋值给变量或者属性。从而使原有的数据仍然保持不变。

强迫自己去思考纯的操作,将函数看作永不会改变数据的闭合功能单元,必然可以减少这种潜在bug的可能性。


函数式编程的优点及其主要的方式


  • 促使将任务分解为简单的函数
  • 使用流式的调用链来处理数据
  • 通过响应式范式降低事件驱动代码的复杂性

1. 将任务分解为简单的函数

这是函数式思考当然入口之一,将一个大的任务分成若干个小任务,然后再分离纯的部分和不纯的部分,将他们各自编写为函数,最后再组合起来。
任务的目标达成。

此时,如果采用链式方式,将每一步链接起来。就好像是一个工厂流水线一样,一头进入原料,一头出现最终产品。
而这也符合数学中的函数,接受参数让x的形态经过函数处理变化为y形态。

函数式编程实际上是分解(将程序拆分为小段)和组合(将小段连接到一起)之间的相互作用。

处理业务逻辑

对于一项业务逻辑:

  1. 把一部分变化低频的部分保存在一个函数中,导出一个接收高频变化数据的函数。
  2. 将这种数据应用于频繁变化的数据。
  3. 组合2这两种函数,解决业务逻辑。
//将变化低频的数据保存在函数中,并导出一个接收高频数据的函数
//注意参数是低频变化数据
function selector(country, school){
    
    //导出一个接收高频变化数据的函数
    return function(student){
        return student.address.country() === country && student.school() === school;
    }
}

//定义个组合函数,作用于这2类数据
function findStudentsBy( friends:student[], selector:(country,school)=>{} ) {
    return friends.filter(selector);
}

//组合2个具体数据,解决业务逻辑

//参数一个是Student类型的实例,一个是定制选择函数
findStudentsBy([curry, turing, church, kleene], selector('US', 'Princeton') );

//结果
//-> [church, kleene]

与单一职责息息相关

OOP中有单一职责原则,如果按照unix的哲学:每个函数只做一件事,并把它做好。那么将功能分成若干个最小功能块,每个是一个函数,即是单一职责的体现。

组合各个功能块

函数式编程中,组合的方式,是将若干个函数组合成为一个函数,然后将一个函数的输出,作为另一个函数的输入。条件是,参与组合的函数,在参数数目和参数类型上要一致。

2. 使用流式链来处理数据

链指的是一串函数的调用,它们共享一个通用的对象返回值。

函数链是一种惰性计算程序,这意味着当需要时才会执行。

3. 复杂异步应用中的响应

主要采用响应式编程的方式解决,例如RxJs,但是函数式编程是其基础。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值