首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

函数式编程

函数式编程(Functional programming)

与面向对象编程(Object-oriented programming)和过程式编程(Procedural programming)并列的编程范式。注:没有过面向过程的精力所有研究

最主要的特征是,函数是第一等公民。

强调将计算过程分解成可复用的函数,典型例子就是map方法和reduce方法组合而成 MapReduce 算法。

只有纯的、没有副作用的函数,才是合格的函数。

函数式编程的起源,是一门叫做范畴论(Category Theory)的数学分支就是关系型数据库的关系,就是数学里关系的概念

理解函数式编程的关键,就是理解范畴论,认为世界上所有的概念体系,都可以抽象成一个个范畴,就是面向对象的编程思想,把所有东西抽象成一个个对象,不过这个对象范畴不是一个概念

那什么是范畴呢

维基百科是这样解释的:"范畴就是使用箭头连接的物体"

百度百科对范畴论的解释:

范畴(category),作为一个数学概念,是一种包含了对象及对象之间箭头的代数结构。范畴具有两个基本性质:一是对象之间的箭头可以复合,且复合是满足结合律的;二是每个对象到自己有一个单位箭头。

一个简单的范畴例子是由集合构成对象,集合间的映射看做箭头。一般来说,对象和箭头可以是抽象的任何类型,范畴的概念提供了一个基本而抽象的方式去研究数学中的对象及其关系的方法

于是我会这样理解==>彼此之间存在着某种关系的概念.事物,对象,都构成"范畴".

也就是==>随便什么东西,只要能找出他们之间的关系,就能够定义一个范畴

上面的各个点与他们之间的箭头,就构成一个范畴

箭头表示范畴之间的关系,正式的名称叫"态射(morphism)"

范畴论认为,同一个范畴的所有成员,就是不同状态的"变形(transformation)"

通过态射,一个成员可以编程另外一个成员

数学模型

既然"范畴"是满足某种变形关系的所有对象,就可以总结出他的数学模型

A .所有成员是一个集合

B.变形关系是函数

也就是说,范畴是集合论更上层的抽象,简单的理解就是"集合+合数"

理论上通过函数,就可以从范畴的一个成员,算出其他成员

范畴与容器

我们可以把范畴想象成一个容器,里面包含两样东西

值(value)

值的变形关系,也就是函数

下面我们使用代码,定义一个简单的范畴

classCate

上面的代码中,Category既是一个类,也是一个容器,里面包含一个值(val)和一中变形关系(addOne)

这时候,所有的范畴,就是彼此之间相差1的数字

注:后文的所有容器,全是指"范畴"

范畴论与函数式编程的关系

范畴论使用函数,表达范畴之间的关系.

伴随着范畴论的发展,就发展出一整套函数的运算方法。这套方法起初只用于数学运算,后来有人将它在计算机上实现了,就变成了今天的"函数式编程"。

本质上,函数式编程只是范畴论的运算方法,跟数理逻辑,微积分,行列式是同一类东西,都是数学的方法,只是碰巧他能用来写程序

所以,为什么函数式编程要求函数必须是纯的,不能有副作用==>就因为他是一种数学运算,原始的目的就是求值,不做其他的事,否则就无法满足函数运算法则了

总之,函数式编程,函数就是一个管道(pipe)==>一头进去一个值,另一头就会出来一个新的值,没有其他作用

函数式编程有两个最基本的运算:合成/柯里化

函数的合成

如果一个值要经过多个函数,才能编程另外一个值,就可以把中间所有的步骤,合并成一个函数,这叫做函数的合成(compose)

如图,X和Y之间的变形关系是函数f,

Y和Z之间的变形关系是函数G

X和Z之间的变形关系是函数g*f

合成两个函数的简单代码如下

函数的合成还必须满足结合律

合成也是函数必须是纯的一个原因

于是,函数合成就是将这些管道连了起来,让数据一口气从多个管道中穿过

柯里化

f(x)和g(x)合成为f(g(x)),有一个隐藏的前提,就是f和g都只能接受一个参数.如果可以接受多个参数,比如f(x,y)和g(a,b,c),函数合成就非常麻烦

这是就需要函数柯里化了,所谓"柯里化",就是把一个多参数的函数,转化为单参数函数

有了柯里化之后,我们就能做到所有函数只能接受一个参数

后文除非另有说明,都默认只有一个参数,就是所要处理的值

函子

函数不仅可以同于一个同一个范畴之中的值转换,还可以用于将一个范畴转换成另外一个范畴,这就设计到了函子(Functor)

函子是函数式编程里面最重要的数据类型,也是最基本的运算单位和功能单位

他先是一种范畴,也就是说是一种容器,包含了值和变形关系,

比较特殊的是,他的变形关系可以依次作用于每一个值,将当前的容器变形成为另外一种容器

上图中,左侧的圆圈就是一个函子,表示任命的范畴,外部传入函数f,会转成右边表示早餐的范畴.

下面是一张更一般的图

上图中,函数f完成值的转换,(a到b),将他传入函子,就可以实现范畴的转换(Fa到Fb)

函子的代码实现

上面代码中,是一个函子,它的方法接受函数作为参数,然后返回一个新的函子,里面包含的值是被处理过的()。

一般约定,函子的标志就是容器具有map方法,改方法就是将容器里面的每一个值,映射到另外一个容器

上面的例子说明,函数式编程里面的运算,都是通过函子完成,即运算不直接针对值,而是针对这个值的容器----函子。函子本身具有对外接口(方法),各种函数就是运算符,通过接口接入容器,引发容器里面的值的变形。

因此,学习函数式编程,实际上就是学习函子的各种运算。由于可以把运算方法封装在函子里面,所以又衍生出各种不同类型的函子,有多少种运算,就有多少种函子。函数式编程就变成了运用不同的函子,解决实际问题

函数式编程

诞生50多年之后,函数式编程(functional programming)也许是继"面向对象编程"之后,"函数式编程"会成为下一个编程的主流定义

简单说,函数式编程是一种"编程范式(programming paradigm)",也就是如何编写程序的方法论

他属于"结构化编程"的一种,主要思想就是把运算过程尽量携程一系列嵌套的函数调用.

比如:

(1+2)*3-4

传统的过程式编程,可能这样写

函数式编程要求使用函数,我们可以把运算过程定义成不同的函数,然后写成下面那样

这就是函数式编程但是也是会影响代码的可读性

特点

函数式编程具有五个鲜明的特点

1.函数是"第一等公民"

所谓"第一等公民(first class)",指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另外一个函数,或者作为别的函数的返回值

这是一段javascript

print就是一个函数,可以作为另外一个函数(使用es6新语法"析构"与遍历)的参数

2.只用"表达式",不用"语句"

"表达式"(expression)只是单纯的运算过程,且含有返回值

"语句"(statement)是执行某种操作,没有返回值,属于对系统的读写操作(I/O)

函数式编程要求,只使用表达式,不使用语句

每一步都是单纯的运算,而且都有返回值

原因是函数式编程的开发动机,一开始是为了处理运算(computation),不考虑系统的读写

即使在实际开发中,I/O操作仍然无法避免.但是函数式编程值要求尽量吧I/O限制最小,不要有不必要的读写行为,保证计算过程的单纯性

3.没有"副作用"

所谓"副作用"(side effect),指的是函数内部与外部互动,(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果

函数式编程强调没有副作用,意味着函数要保持独立,所有功能就是一个返回值,没有其他行为,尤其是不能修改外部变量的值

4.不修改状态

函数式编程知识返回新的值,不修改系统变量.因此,不修改变量,也是他的一个重要特点

在其他语言里,变量往往用来保存"状态(state)".不修改变量,意味着状态不能保存在变量中.函数式编程使用参数保存状态,最好的例子就是递归

这是一段将字符串逆排序的函数,演示了不同参数如何决定了运算所处的状态

由于使用了递归,函数式语言的运行速度比较慢,这是他长期不能在业界推广的主要原因

5.引用透明

引用透明(Referential transparency),指的是

函数的运行不依赖于外部变量或状态,值依赖输入的参数,任何时候只要参数相同,引用函数所得的返回值总是相同的

有了前面的第三点和第四点,这点是很显然的。其他类型的语言,函数的返回值往往与系统状态有关,不同的状态之下,返回值是不一样的。这就叫"引用不透明",很不利于观察和理解程序的行为。

函数式编程的意义

说意义,其实也是也说他的好处,为什么存在,又为什么开始流行开来

1.代码简介,开发速度快

函数式编程大量使用了函数,减少了代码的重复量,因此程序比较短,开发程序更快

2.接近自然语言,易于理解

函数式编程的自由度很高,可以写出很接近自然的代码

参考前文的(1+2)*3-4写成的函数式语言

3.更方便的代码管理

函数式编程不依赖也不改变外部状态,只需要给定参数,返回结果必然相同.

因此每个函数都可以被看做独立运算的单元,很有利于单元测试(unit testing)和除错(debugging),以及模块化组合

4.易于并发编程

函数式编程不需要考虑:"死锁"(deadlock),因为他不修改变量,所以不存在"锁"住线程的问题

不必担心一个线程的数据,被另外一个线程修改,

所以可以很放心的把工作分摊到多个线程,部署并发编程(concurrency)

由于s1和s2互不干扰,不会修改变量,谁先执行是无所谓的,所以可以放心地增加线程,

把它们分配在两个线程上完成。其他类型的语言就做不到这一点,因为s1可能会修改系

统状态,而s2可能会用到这些状态,所以必须保证s2在s1之后运行,自然也就不能部署

到其他线程上了。

5.代码的热升级

函数式编程没有副作用,只要保证接口不变,颞部实现是外部无关的

所以,可以在运行状态下直接升级代码,不需要重启,也不需要停机

文章参考:

以及维基百科百度百科

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20171211G0FON700?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券