paip.函数式编程方法概述以及总结
paip.函数式编程方法概述以及总结
1 函数式编程:函数式风格..很多命令式语言里支持函数式编程风格... 1
2 命令式语言(java,c#,php等)里使用函数式编程风格... 3
作者Attilax 艾龙, EMAIL:1466519819@qq.com
来源:attilax的专栏
地址:http://blog.csdn.net/attila
1 函数式编程:函数式风格.. 很多命令式语言里支持函数式编程风格
于函数编程(functionalprogramming——FP)
函数式编程(英语:Functionalprogramming)或者函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。函数编程语言最重要的基础是λ演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。
函数式编程是种编程典范,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ 演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。和过程化编程相比,函数式编程里,函数的计算可随时调用。
迭代。命令式编程利用迭代来处理许多不同类型的数据结构。许多命令式控制依赖破坏性赋值进行迭代。
除了了构造函数之外,不再提供任何的可变方法。
你不仅要避免典型的受JavaBeans启发的setXXX方法,还必须注意不要返回可变的对象引用。对象引用被声明成final的,这是实情,但这并不意味这你不能改变它所指向的内容。因此,你需要确保你是防御性地拷贝了从getXXX方法中返回的任何对象引用。
1.1 起源 (图灵机,Lisp机器, 神经网络计算机)
冯·诺曼架构就是你每天都用的计算机的架构的标准:一个CPU,内存,硬盘,一条总线。多核计算机并没有带来本质上的变化。冯·诺曼机是一个很方便,很便宜,上世纪五十年代的实现图灵机的技术,图灵机是执行计算的最知名的抽象模型。
世 上还有其他的计算的机器。比如,Lisp机器,是上世纪50年代对Lisp计算模型的实现。Lisp模型是基于lambda代数的一种计算语言表示法,后 者是与图灵机同构的一种模型。不像图灵机,lambda代数能被人类读和写。但是这二者是同等能力的。它们同样精确的表示了计算机能干什么。
了。还有一些其他的计算机,比如神经网络计算机,译者也不知道怎么翻的计算机(cellular automata),但是这些都不够大众化,至少现在是这样的。
1.2 函数式编程语言有哪些
Haskell,Erlang,还是Scala,F#,都 APL XSLT Erlang 这些年来,广大程序员都忽视了JavaScript也是一门动态类型语言,还是一门函数语言!
2 命令式语言(java,c#,php等)里使用函数式编程风格
很多命令式语言里支持函数式编程风格。换句话说,它不是函数式编程语言的专利(尽管它们更适合)。我们应该清楚的区分这两个概念,从而避免对函数式编程语言和命令式编程语言之间的不同产生混淆。
,命令式编程这个名字是从自然语言(比如英语)的 祈使语气(imperative mood)衍生出来的,在这种语气中宣布命令并按照执行 函数编程是一种强调表达式的计算而非命令的执行的一种编程风格。表达式是用函数结合基本值构成的,它类似于用参数调用函数。
3 函数式编程应用场合
我们看完了函数式编程的特点,我们想想函数式编程的应用场合。
1. 数学推理
2. 并行程序
3.热部署:这点其实许多脚本语言也能热部署,但是脚本性能底下.
那么我们总体地说,其实函数式编程最适合地还是解决局部性的数学小问题,要让函数式编程来做CRUD,来做我们传统的逻辑性很强的Web编程,就有些免为其难了。
4 函数式编程的特点
4.1 函数是函数式编程的基本单位
,所以函数式编程主要处理计算,并惊人地用函数来完成这一过程。函数是函数式编程的基本单位,函数几乎被用于一切,包括
最简单的计算,甚至变量都由计算取代。在函数式编程中,变量只是表达式的别名(这样我们就不必把所有东西打在一行里)。变量是不能更改的,所有变量只能被
赋值一次
4.2 保存状态
函数式程序能保存状态,只是它并非通过变量而是使用函数来保存状态。状态保存在函数的参数中,保存在堆栈上。如果你要保存某个状态一段时间并
4.3 函数式编程的抽象本质
相信每个程序员都对抽象这个概念不陌生。
在面向对象编程中,我们说,类是现实事物的一种抽象表示。那么抽象的最大作用在我看来就在于抽象事物的重用性,一个事物越具体,那么他的可重用性就越低,因此,我们再打造可重用性代码,类,类库时,其实在做的本质工作就在于提高代码的抽象性。而再往大了说开来,程序员做的工作,就是把一系列过程抽象开来,反映成一个通用过程,然后用代码表示出来。
在面向对象中,我们把事物抽象。而在函数式编程中,我们则是在将函数方法抽象,第六节的滤波器已经让我们知道,函数一样是可重用,可置换的抽象单位。
那么我们说函数式编程的抽象本质则是将函数也作为一个抽象单位,而反映成代码形式,则是高阶函数
4.4 -----结果比步骤更重要
函数式编程的特点之一是存在强大的抽象,它隐藏了许多日常操作的细节(比如迭代)。
4.5 ----少量数据结构,大量操作
在面向对象的命令式编程语言中,重用的单元是类以及与这些类进行通信的消息,这些信息是在类图中捕获的。该领域的开创性著作是 Design Patterns: Elements of Reusable Object-Oriented Software(参阅 参考资料),至少为每个模式提供一个类图。在 OOP 的世界中,鼓励开发人员创建独特的数据结构,以方法的形式附加特定的操作。函数式编程语言尝试采用不同的方式来实现重用。它们更喜欢一些关键的数据结构(如列表、集和映射),并且在这些数据结构上采用高度优化的操作。传递数据结构和高阶函数,以便“插入” 这种机制,针对某一特定用途对其进行定制。例如,在清单 2 中,findAll() 方法接受使用一个代码块作为“插件” 高阶函数(该函数确定了筛选条件),而该机制以有效方式应用了筛选条件,并返回经过筛选的列表。
4.6 种数据驱动型的编程语言:
一条指令的运算结果又流向下一条指令,作为下一条指令的操作数来驱动此指令的启动执行;(管道的思想)
能充分地利用程序中指令级并行性;
不存在共享数据,也不存在指令计数器,指令启动执行的时机仅取决于操作数具备与否
只要有足够多的处理单元,凡是相互间不存在数据相关的指令都可以并行执行。因此,函数式语言的这种数据驱动的编程思想使得它们的语言在并行处理方面具有与生俱来的优势。
4.7 ----函数式编程特点(高阶函数)
在面向对象编程中,我们把对象传来传去,那在函数式编程中,我们要做的是把函数传来传去,而这个,说成术语,我们把他叫做高阶函数。
在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:
- 接受一个或多个函数作为输入
- 输出一个函数
在函数式编程中,函数是基本单位,是第一型,他几乎被用作一切,包括最简单的计算,甚至连变量都被计算所取代。在函数式编程中,变量只是一个名称,而不是一个存储单元,这是函数式编程与传统的命令式编程最典型的不同之处。
函数编程支持函数作为第一类对象,有时称为闭包或者仿函数(functor)对象。实质上,闭包是起函数的作用并可以像对象一样操作的对象。与此类似,FP 语言支持高阶函数。高阶函数可以用另一个函数(间接地,用一个表达式)作为其输入参数,在某些情况下,它甚至返回一个函数作为其输出参数。这两种结构结合在一起使得可以用优雅的方式进行模块化编程,这是使用 FP 的最大好处。
4.8 ///传递行为,而不仅仅是传值
你可以把一个函数看做是一组指令,也就是说,你可以把一组指令告诉另一个函数。想想在很多其他编程语言里面,它只允许你把一些数据告诉另一个函数。举
如果你使用C#有一段时间的话,那么你很可能已经明白这个标题的意思了。在C#中,经常看到一些函数的参数是Action或者Func类型,比如下面这个:
这是java在解决函数式编程,引入lambda表达式的同时引入的一个概念,具体的意思就是,定义的一个接口,接口里面必须有且只有一个方法,这样的接口就成为函数式接口。在可以使用lambda表达式的地方,方法声明时必须包含一个函数式的接口。任何函数式接口都可以使用lambda表达式替换。 下面来看lambda的基本逻辑:
button.onAction(newEventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent e) {
doSomethingWith(e);
}
});
4.9 闭包, 消息
闭包, 消息
Runnable worker = new Runnable()
{
public void run()
{
parseData();
}
};
方法 parseData
确实封闭(因而有了名字 “闭包”)在 Runnable
对象的实例 worker
中。它可以像数据一样在方法之间传递,并可以在任何时间通过发送消息(称为run
)给 worker
对象而执行
创建闭包
广意地说,有两种生成闭包的技术,使用闭包的代码可以等效地使用这两种技术。创建闭包后,可以以统一的方式传递它,也可以向它发送消息以让它执行其封装的逻辑。因此,技术的选择是偏好的问题,在某些情况下也与环境有关
在第一种技术 表达式特化(expressionspecialization)中,由基础设施为闭包提供一个一般性的接口,通过编写这个接口的特定实现创建具体的闭包。在第二种技术 表达式合成(expression composition)中,基础设施提供实现了基本一元 / 二元 / 三元 /.../n 元操作(比如一元操作符 not和二元操作符 and/ or)的具体帮助类。在这里,新的闭包由这些基本构建块的任意组合创建而成。
Apache Functor Java 语言中的函数编程.htm
4.10 / 内部循环和外部循环
外循环、内循环和Map、Reduce、Filter
一直到现在,处理Java集合的标准做法是采用外循环。
顺序处理(sequential handling)。顺序特性也常常引发ConcurrentModificationException
先看一个大家耳熟能详的例子:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
for (int number : numbers) {
System.out.println(number);
}
是不是很常见呢?这个叫外部循环(External Iteration)。但是外部循环有什么问题呢?简单来说存在下面三个缺点:
1.只能顺序处理List中的元素(processone by one)
2.不能充分利用多核CPU
3.不利于编译器优化
而如果利用内部循环,代码写成下面这样:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.forEach((Integer value) -> System.out.println(value));
这样就能规避上面的三个问题:
1.不一定需要顺序处理List中的元素,顺序可以不确定
2.可以并行处理,充分利用多核CPU的优势
3.有利于JIT编译器对代码进行优化
类似的C#从4.0版本开始也支持集合元素并行处理,代码如下:
List<int> nums = new List<int> { 1, 2, 3, 4, 5, 6 };
Parallel.ForEach(nums,(value) =>
{
Console.WriteLine(value);
});
4.11 10. 函数式编程和递归
和命令式编程相比,函数式编程强调程序的执行结果比执行过程更重要,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程
递归是函数式编程的一个重要的概念,循环可以没有,但是递归对于函数式编程却是不可或缺的。
在这里,我得承认,我确实不知道我该怎么解释递归为什么对函数式编程那么重要。我能想到的只是递归充分地发挥了函数的威力,也解决了函数式编程无状态的问题。(如果大家有其他的意见,请赐教)
递归其实就是将大问题无限地分解,直到问题足够小。
而递归与循环在编程模型和思维模型上最大的区别则在于:
循环是在描述我们该如何地去解决问题。
递归是在描述这个问题的定义
这里则是在描述我们该如何求解斐波那契数列,应该先怎么样再怎么样。
而我们明显可以看到,递归相比于循环,具有着更加良好的可读性。
但是,我们也不能忽略,递归而产生的StackOverflow,而赋值模型呢?我们懂的,函数式编程不能赋值,那么怎么办
4.12 2. 惰性求值与并行
第一次接触到惰性求值这个概念应该是在Haskell语言中,看一个最简单的惰性求值,我觉得也是最经典的例子:
在Haskell里,有个repeat关键字,他的作用是返回一个无限长的List,那么我们来看下:
take 10 (repeat 1)
就是这句代码,如果没有了惰性求值,我想这个进程一定会死在那里,可是结果却是很正常,返回了长度为10的List,List里的值都是1。这就是惰性求值的典型案例。
我们看这样一段简单的代码:
def getResult():
a = getA() //Take a long time
b = getB() //Take a long time
c = a + b
这段代码本身很简单,在命令式程序设计中,编译器(或解释器)会做的就是逐一解释代码,按顺序求出a和b的值,然后再求出c。
可是我们从并行的角度考虑,求a的值是不是可以和求b的值并行呢?也就是说,直到执行到a+b的时候我们编译器才意识到a和b直到现在才需要,那么我们双核处理器就自然去发挥去最大的功效去计算了呢!
这才是惰性求值的最大威力
4.13 11. 尾递归,伪递归
我们之前说到了递归和循环各自的问题,那怎么来解决这个问题,函数式编程为我们抛出了答案,尾递归。
什么是尾递归,用最通俗的话说:就是在最后一部单纯地去调用递归函数,这里我们要注意“单纯”这个字眼。
那么我们说下尾递归的原理,其实尾递归就是不要保持当前递归函数的状态,而把需要保持的东西全部用参数给传到下一个函数里,这样就可以自动清空本次调用的栈空间。这样的话,占用的栈空间就是常数阶的了。
在看尾递归代码之前,我们还是先来明确一下递归的分类,我们将递归分成“树形递归”和“尾递归”,什么是树形递归,就是把计算过程逐一展开,最后形成的是一棵树状的结构,
4.14 可读性 oop vs fp
面向对象的编程通过封装可变动的部分来构造出可让人读懂的代码,函数式编程则是通过最小化可变动的部分来构造出可让人读懂的代码。
5 优点
5.1 (多核并行)
多核并行程序设计就这样被推到了前线,而命令式编程天生的缺陷却使并行编程模型变得非常复杂,无论是信号量,还是锁的概念,都使程序员不堪其重。
函数式程序无需任何修改即可并行执行。不用担心死锁和临界区,因为你从未用锁!函数式程序里没有任何数据被同一线程修改两次,更不用说两个不同的线程了。这意味着可以不假思索地简单增加线程而不会引发折磨着并行应用程序的传统问题。
5.2 代码热部署
理想的情况是完全不停止系统任何组件来更新相关的代码。在命令式的世界里这是不可能的。考虑运行时上载一个Java类并重载一个新的定义,那么所有
这个类的实例都将不可用,因为它们被保存的状态丢失了。我们可以着手写些繁琐的版本控制代码来解决这个问题,然后将这个类的所有实例序列化,再销毁这些实
例,继而用这个类新的定义来重新创建这些实例,然后载入先前被序列化的数据并希望载入代码可以恰到地将这些数据移植到新的实例。在此之上,每次更新都要重
新手动编写这些用来移植的代码,而且要相当谨慎地防止破坏对象间的相互关系。理论简单,但实践可不容易。
对函数式的程序,所有的状态即传递给函数的参数都被保存在了堆栈上,这使的热部署轻而易举!实际上,所有我们需要做的就是对工作中的代码和新版本的
代码做一个差异比较,然后部署新代码。其他的工作将由一个语言工具自动完成!如果你认为这是个科幻故事,请再思考一下。多年来
Erlang工程师一直更新着他们的运转着的系统,而无需中断它
5.3 机器辅助的推理和优化
函数式语言的一个有趣的属性就是他们可以用数学方式推理。因为一种函数式语言只是一个形式系统的实现,所有在纸上完成的运算都可以应用于用这种语言书写的程序。编译器可以用数学理论将转换一段代码转换为等价的但却更高效的代码[7]。多年来关系数据库一直在进行着这类优化
5.4 提高代码可读性
特别是解决了重复命名变量的问题。
6 缺点
F#也许终将成为程序核心数据多线程处理的首选,而C#与VB等将在用户界面交互设计方面继续发挥其强大的潜力。但C# 依然是.NET 的主流设计语言。
7 参考
函数式编程扫盲篇 - 飞林沙 - 博客园.htm
函数式编程的优点 -rxr1st的专栏 - 博客频道 - CSDN.NET.htm