我的FP感悟
FP概要:
我主要总结了以下5点:
- 函数是一等公民: 函数的参数是函数,返回值是函数,类型还是函数...
- 舍弃语句,拥抱表达式: 表达式就一定有返回值。
- 无副作用: 不修改外部系统的状态。
- immutable: 没有变量,一切不可变,循环用递归实现。
- 引用透明: 相同输入产生相同输出,需要的信息都通过参数传递,方便单元测试.
就以上几点,我编了一首打油诗:
函数编程很强大
一等公民都是它
外部变量不依赖
返回确保串行化
串行化什么意思呢?
同样计算(1+2)*3/4
,java代码如下:
int a = 1 + 2
int b = a * 3
int c = b / 4
而Scala写出来如下:
val r = subtract(multiply(add(1, 2), 3), 4)
如此,就可以很简单的改写为:
val r = add(1, 2).multiply(3).subtract(4)
其中的美妙自己体会吧~~~
那么这么做的意义呢?
- 代码简洁
- 接近自然语言
- 函数单纯,易于测试
- immutable,并发简单可控
- 热升级
- 如果代码包含了任何的var,则是指令式风格,如果全是val则可能是函数式风格。
- 函数的结果类型是Unit的是有副作用的,而结果类型有明确返回类型的则可能是没有副作用的。
- 函数内部依赖外部数据而不是输入参数(也就是闭包)的是有副作用的, 而不依赖外部数据的可能没有副作用.
函数式编程的历史
有机会看到这篇文章的读者,大概都会知道阿兰·图灵(Alan Turing)和约翰·冯·诺伊曼(John von Neumann)。阿兰·图灵提出了图灵机的概念,约翰·冯·诺伊曼基于这一理论,设计出了第一台现代计算机。
由于图灵以及冯·诺伊曼式计算机的大获成功,历史差点淹没了另外一位同样杰出的科学家和他的理论,那就是阿隆佐·邱奇(Alonzo Church)和他的λ演算。
阿隆佐·邱奇是阿兰·图灵的老师,上世纪三十年代,他们一起在普林斯顿研究可计算性问题,为了回答这一问题,阿隆佐·邱奇提出了λ演算,其后不久,阿兰·图灵提出了图灵机的概念,尽管形式不同,但后来证明,两个理论在功能上是等价的,条条大路通罗马。
如果不是约翰·麦卡锡(John McCarthy),阿隆佐·邱奇的λ演算恐怕还要在历史的故纸堆中再多躺几十年,约翰·麦卡锡是人工智能科学的奠基人之一,他发现了λ演算的珍贵价值,发明了基于λ演算的函数式编程语言:Lisp,由于其强大的表达能力,一推出就受到学术界的热烈欢迎,以至于一段时间内,Lisp 成了人工智能领域的标准编程语言。
很快,λ演算在学术界流行开来,出现了很多函数式编程语言:Scheme 、SML、Ocaml 等,但是在工业界,还是命令式编程语言的天下,Fortran、C、C++、Java 等。
随着时间的流逝,越来越多的计算机从业人员认识到函数式编程的意义,爱立信公司于上世纪八十年代开发出了 Erlang 语言来解决并发编程的问题;
在互联网的发展浪潮中,越来越多的语言也开始支持函数式编程:JavaScript、Python、Ruby、Haskell、Scala 等。
可以预见,如果过去找一个懂什么是函数式编程的程序员很困难,那么在不久的将来,找一个一点也没听过函数式编程的程序员将更加困难。
面向对象编程
对于面向对象,我们已经再熟悉不过了,所以这里不做过多介绍,直接分析它的优缺点.
面向对象编程的优点
面向对象程序设计可以看作一种在程序中包含各种独立而又互相调用的对象的思想(抽象现实世界),这与传统的思想刚好相反。
传统的程序设计主张将程序看作一系列函数的集合,或者直接就是一系列对电脑下达的指令。
面向对象程序设计中的每一个对象都应该能够接受数据、处理数据并将数据传达给其它对象,因此它们都可以被看作一个小型的“机器”,即对象。
目前已经被证实的是,面向对象程序设计推广了程序的灵活性和可维护性,并且在大型项目设计中广为应用。
此外,支持者声称面向对象程序设计要比以往的做法更加便于学习(所以易于推广),因为它能够让人们更简单地设计并维护程序,使得程序更加便于分析、设计、理解。
同时它也是易拓展的,由于继承、封装、多态的特性,自然设计出高内聚、低耦合的系统结构,使得系统更灵活、更容易扩展,而且成本较低。
在面向对象编程的基础上发展出来的23种设计模式广泛应用于现今的软件工程中,极大方便了代码的书写与维护。
- 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
- 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
面向对象编程的缺点
- (多并发问题)面向对象编程以数据为核心,所以在多线程并发编程中,多个线程同时操作数据的时候可能会导致数据修改的不确定性。
- 在现在的软件工程中,由于面向对象编程的滥用,导致了很多问题。
- (冗余度大)首先就是为了写可重用的代码而产生了很多无用的代码,导致代码膨胀.
- (难理解,难维护)同时很多人并没有完全理解面向对象思想,为了面向对象而面向对象,使得最终的代码晦涩难懂,给后期的维护带来了很大的问题。
- (大型系统不适应)所以对于大项目的开发,使用面向对象会出现一些不适应的情况。
- (低效率)面向对象虽然开发效率高但是代码的运行效率比起面向过程要低很多,这也限制了面向对象的使用场景不能包括那些对性能要求很苛刻的地方。
下面我们在看看函数式编程(FP)
什么是函数式编程(FP)
狭义地说,函数式编程没有可变的变量、循环等这些命令式编程方式中的元素,像数学里的函数一样,对于给定的输入,不管你调用该函数多少次,永远返回同样的结果。而在我们常用的命令式编程方式中,变量用来描述事物的状态,整个程序,不过是根据不断变化的条件来维护这些变量。
广义地说,函数式编程重点在函数,函数是这个世界里的一等公民,函数和其他值一样,可以到处被定义,可以作为参数传入另一个函数,也可以作为函数的返回值,返回给调用者。利用这些特性,可以灵活组合已有函数形成新的函数,可以在更高层次上对问题进行抽象。本文的重点将放在这一部分。
函数式编程的优点
- (不可变,易并发)在函数式编程中,由于数据全部都是不可变的,所以没有并发编程的问题,是多线程安全的。
- (数据一致,结果稳定)可以有效降低程序运行中所产生的副作用,对于快速迭代的项目来说,函数式编程可以实现函数与函数之间的热切换而不用担心数据的问题,因为它是以函数作为最小单位的,只要函数与函数之间的关系正确即可保证结果的正确性。
- (可读性强)函数式编程的表达方式更加符合人类日常生活中的语法,代码可读性更强。
实现同样的功能函数式编程所需要的代码比面向对象编程要少很多,代码更加简洁明晰。 - 函数式编程广泛运用于科学研究中,因为在科研中对于代码的工程化要求比较低,写起来更加简单,所以使用函数式编程开发的速度比用面向对象要高很多,如果是对开发速度要求较高但是对运行资源要求较低同时对速度要求较低的场景下使用函数式会更加高效。
函数式编程的缺点
- (变量消耗内存)由于所有的数据都是不可变的,所以所有的变量在程序运行期间都是一直存在的,非常占用运行资源。
- (运行速度慢)同时由于函数式的先天性设计导致性能一直不够。虽然现代的函数式编程语言使用了很多技巧比如惰性计算等来优化运行速度,但是始终无法与面向对象的程序相比,当然面向对象程序的速度也不够快。
- (不普及)函数式编程虽然已经诞生了很多年,但是至今为止在工程上想要大规模使用函数式编程仍然有很多待解决的问题,尤其是对于规模比较大的工程而言。如果对函数式编程的理解不够深刻就会导致跟面相对象一样晦涩难懂的局面。
总结
函数式编程和面向对象编程各有利弊,一个语法更加自由(FP),一个健壮性更好(OOP)。作为程序员应该对两种编程方式都有所了解,不管是哪种方式,只要能够很好的解决当前的问题就是正确的方式,毕竟对于软件工程来说解决问题是最主要的,用的工具反而没有那么重要,就像对程序员来说语言不重要,重要的是解决问题的思想。
现在这两者的发展趋势是相互借鉴的,许多以面向对象作为基础的语言例如Java等都在新的版本中添加了对函数式编程的支持,而函数式编程则借鉴了一些在面向对象语言里用的一些编译技巧使得程序运行更快。
细说FP的优点
约翰·巴克斯(John Backus)为人熟知的两项成就是 FORTRAN 语言和用于描述形式系统的巴克斯范式,因为这两项成就,他获得了 1977 年的图灵奖。
有趣的是他在获奖后,做了一个关于函数式编程的讲演:Can Programming Be Liberated From the von Neumann Style? 1977 Turing Award Lecture。他认为像 FORTRAN 这样的命令式语言不够高级,应该有新的,更高级的语言可以摆脱冯诺依曼模型的限制,于是他又发明了 FP 语言,虽然这个语言未获成功,但是约翰·巴克斯关于函数式编程的论述却得到了越来越多的认可。下面,我们就罗列一些函数式编程的优点。
首先,函数式编程天然有并发的优势。由于工艺限制,摩尔定律已经失效,芯片厂商只能采取多核策略。程序要利用多核运算,必须采取并发,而并发最头疼的问题就是共享数据,狭义的函数式编程没有可变的变量,数据只读不写,并发的问题迎刃而解。这也是前面两篇文章中,一直建议大家在定义变量时,使用 val 而不是 var 的原因。爱立信公司发明的 Erlang 语言就是为解决并发的问题而生,在电信行业取得了不俗的成绩。
其次,函数式编程有迹可寻。由于不依赖外部变量,给定输入函数的返回结果永远不变,对于复杂的程序,我们可以用值替换的方式(substitution model)化繁为简,轻松得出一段程序的计算结果。为这样的程序写单元测试也很方便,因为不用担心环境的影响。
最后,函数式编程高屋建瓴。写程序最重要的就是抽象,不同风格的编程语言为我们提供了不同的抽象层次,抽象层次越高,表达问题越简洁,越优雅。读者从下面的例子中可以看到,使用函数式编程,有一种高屋建瓴的感觉。