一、核心思想
函数式编程语言,让我们用高阶抽象取代基本的控制结构。
面向对象编程通过封装不确定因素使代码被人理解;函数式编程通过尽量减少不确定因素使代码被人理解。 ---- Micheal Feathers
面向对象编程:重用的单元是类和类之间的沟通消息(方法)。OOP提倡开发者针对具体问题建立专门的数据结构,以专门的操作(方法)附在数据结构上。
函数式编程:提倡在有限几种关键数据结构(List,Map,set)上针对这些数据结构进行高度优化的操作(方法),以此构成基本运转机制。开发者根据具体的用途插入自己的数据结构和高阶函数去调整整体机制的运转方式。
二、思维转变
学习新的语言,把熟悉的概念用新的语法表达出来。
学习新的范式,为熟悉的问题找到新的解答方法。
函数式编程,需要站在更高的抽象层次上工作。
命令式编程:按照程序是一系列状态改变的命令
来建模的一种编程风格。例如 for 循环 ,先确认初始状态,每次都执行每次迭代循环体中的一系列命令。命令式编程鼓励将操作安排在循环内部进行。
函数式编程:将程序描述为表达式和变换,以数学的方式建立模型,尽量避免可变的状态。
高阶函数消除了摩擦
函数式的基本构造单元
- 筛选 (filter)
需要根据筛选条件产生一个子集合 使用filter - 映射(map)
对原集合中的每一个元素执行给定的函数,从而变成一个新的集合. - 折叠和化约(fold,reduce)
用一个累积量来"收集"集合元素.比如fold的含义为用一个二元函数或者云算符结合列表的首元素和累积量的初始值,重复上一步直到列表耗尽,此时累积量的取值为折叠运算的结果.
需要把一个集合分成一小块一小块的来处理,使用fold或reduce
三、权责让渡
抽象的目的总是一样的:让开发者从繁琐的细节中解脱出来,去解答问题中非重复性的那些部分。
1.迭代让位高阶函数
2.闭包
闭包:是一种奇特的函数,他把需要引用的内部变量都放在上下文里“包”起来了。换句话说,对于所需的内部变量可以让调用者灵活创建。一般闭包包括两个步骤,创建绑定和执行。
Groovy中闭包示例:
class Employee {
def name, salary
}
//创建闭包
def paidMore(amount) {
return { Employee e -> e.salary > amount }
}
//绑定闭包
isPaidMore=paidMore(10000)
//执行闭包
def lily = new Employee("Lily", 15000)
println(isPaidMore(lily))
//绑定闭包
isPaidMore2=paidMore(20000)
//执行闭包
def lucy = new Employee("Lucy", 20000)
println(isPaidMore2(lucy))
这里面 paidMore就是一个闭包,isPaidMore,isPaidMore2是闭包的实例。
每个闭包实例中保有自己的一份变量取值,包括私有变量也是如此。
闭包经常被函数式语言和框架当做一种异地执行的机制,用来传递待执行的变换代码,例如map之类的高阶函数
-
科里化和函数的部分施用
科里化:如果你固定某些参数,你将得到接受余下参数的一个函数。对于一个两个可变量的函数xy,如果固定y=2,将得到有一个可变量的函数2x。一般情况f(x,y,z)=>f(x)(y)(z) 把函数多个参数变成一连串单个参数的变换。
部分施用:提前带入一部分参数值,使得多个参数的函数得以省略部分参数,从而得到单个参数的函数。先求解一部分参数,返回由剩下参数构成签名的函数。一般情况,如果在f(x,y,z)上部分施用一个参数,将得剩余两个参数的函数f(y,z)。
image.png3.科里化和部分施用一般用途
1.函数工厂
在传统面向对象
image.png
2.模板方法模式
GoF的模板方法(Template Method)的用意是在固定的算法框架内部安排一些抽象方法,为后续具体实现保留一部分灵活行。部分施用先固定一部分参数(注入当前已确定的行为),留下未确认的参数给具体实现去发挥,思路和模板方法如出一辙。
3.隐含参数
当我们需要频繁调用一个函数,而每次参数值都差不多时,使用科里化来设置隐含参数。【把参数值固定】
4.递归
递归:“以一种自相似的方式来重复事物的过程”。
是运行时托付细节的一个例子,而且和函数式编程有着紧密联系。
四. 函数的两种常见特性
函数式编程的构造目的:从频繁出现的场景中 消灭掉烦人的细节
1.记忆
指的是在函数级别上对多次需要使用的值进行缓存的策略.
缓存两种用法:
- 内部缓存
- 外部缓存
缓存的两种实现方式
- 手工实现状态管理
- 自动记忆机制
2.引入记忆
记忆函数,使用了所谓"元函数"技法(操作的对象是函数本身,而非函数结果)
3.缓求值
缓求值的集合不会预先算好所有的元素,而是在需要用到的时候才落实下来.
有3大好处:
- 昂贵的运算只有到了绝对必要时候才执行
- 可以建立无限大集合,只要一有请求,就一直送出元素
- 按缓求值的方式来使用映射,过滤等函数可以产生高效代码
4.构造缓求值列表
对于严格求值的语言,如果使用闭包递归的将一个严格求值列表层层包裹起来,就可以将之变成缓求值列表.现实中会避免施用递归.
五.模式与重用
大多数现代语言是多范式,支持对象,函数式,元对象,以及其他多样化范式.
在不同的范式中表现出来的设计模式,可能呈现截然不同的外在形象.这是因为函数式世界用来搭建程序的材料不一样,所以解决问题的手段也不同.GoF的模式有一部分传统模式失去了存在的意义,还有一部分,他们要解决的问题还存在,只是解决手段不一样.
传统的设计模式在函数式编程世界中大致有3中归宿:
- 模式已经被吸收成语言的一部分
- 模式中描述解决的办法依然还成立,具体的实现细节有变化
- 由于新语言或者范式获得了原本没有的能力,有了新的解决方案.(例如元编程)
1.函数级别的重用
复用(composition),作为一种重用机制,在函数式语言中主要表现为:通过参数传递函数.与面向对象语言相比,函数式语言重用发生在粗粒度的级别上,着眼提取共通的运行机制,并参数化调整其行为.
函数式的重用机制建立在列表的概念,以及可以连同上下文一期传递的代码块的概念之上.
函数式语言作为第一等成分的函数,可以充当参数和返回值.
- 函数式模板模式(Template Method)
- Strategy模式
- Flyweight 模式和记忆
- 单例模式
- Factory模式和科里化
2.结构化重用和函数式重用的对比
1.以结构为载体的代码重用
如果面向对象的代码值得重用,我们一般会把他提取到另一个类中,然后通过继承来访问它.
六.现实应用
- 函数式接口
含有单一方法的接口,称为函数式接口SAM(single Abstract Method),runnable 和callable接口就是有代表性的例子.在java中允许通过lambda 和SAM携手. - Optional类型
Optional 是一种结构,防止返回结果出现null - Stream
函数式的基础设施
在数据库,软件架构上函数是思维的作用?
1.架构
函数式的架构从根本上贯彻"值不可变"的思路,最大的发挥其优点.学会从值不变的角度去思考,是我们掌握函数式思维的一条重要门径.
- CQRS
传统应用 程序架构把读写数据交织一起.例如
坏处:模型需要负责处理业务规则和验证的工作,一般模型对象还要在内部或通过另外的逻辑层来协调持久化事项.开发者需要兼顾读写两方面潜在的影响,增加复杂性.
CQRS 通过分离架构中负责读取的部分和负责命令的部分,部分的简化了程序的架构.
架构永远是取舍的结果,CQRS简化了一些方面,同时又复杂话了另一些方面.例如对于集中式数据库来说,事务处理并不难.可是在CQRS架构下,我们可能需要用最终一致性模型来取代事务性的模型.
最终一致性:是分布式中的一种模型,不对模型的变更操作施加硬性的时间限制,而只保证,当更新发生后,模型最终回复到一致的状态.
2.Web框架
web领域与函数式编程简直是天作之合.这些web框架大多具备以下共同特性:
- 路由框架
从应用主体中剥离路由的相关细节,将之交托专门的路由功能库. - 以函数作为路由的目标
把路由理解成一个接收request,返回response的函数. - 领域专用预言(DSL)
MartinFowler 将DSL定义为表达能力有限,专门针对一个狭窄问题领域的计算机编程语言.函数式预言偏好描述性的代码风格这一点恰好也常常是DSL的目的. - 与构建工具紧密集成
总体来说,不可变的值编程,会降低测试的负担,因为需要通过测试来验证的状态变化少了.
七.多语言与多范式
现代编程语言常常是多范式的,支持多种多样的编程范式,如面向对象,元编程,函数式,过程式等等.
正交 在数学里吧两个相互垂直的向量称作正交,也就是说这两个向量不相关(不会相交).在计算机科学里,把两个组件如果没有相互没有任何影响(或副作用),就可以称作是正交的.例如在groove,kotlin 中使用元编程并不妨碍我们使用函数式编程的构造,反之亦然.
2.多范式预言的后顾之忧
由于预言支持各式各样的抽象和概念,由不同的开发群体制作出来的库也会呈现明显的差异.不同范式的思路是不一样的.可以运用"消费者驱动的契约",(以测试的形式)在不同团队之间简历可执行的契约.
消费者驱动的契约:
一项集成工作的实施方与各组件供应方共同商定的一组测试.集成方"同一",如果一方需要打破前设置条件,必须着急所受欧影响的当事方,议定一组新的测试.
3.上下文型抽象与复合型抽象对比
复合是函数是编程领域奉为圭臬的设计原则.
上下文型是 基于插件的架构使用的设计原则.
4.函数是金字塔
进行语言的选型
语言的选型.png不应该从静态类型中求索抵御错误的能力,从根本上拥抱函数式的概念才是正确方向.假如包括数据访问,集成等重要职责在内的核心api,都能以值不变性为前提来设计的话,那么所有的代码都对大幅度地简化.当然在这思路下,数据库和其他基础设施的构建方式也需要随之放生变化.
在函数式内核之上,用命令式语言编写系统中对开发效率要求较高的部分,例如工作流,业务规则,用户界面.最上层和原来模型一样是 DSL层,DSL会贯穿系统的所有层次,一致深入到最底层.
网友评论