诞生50多年之后,函数式编程(functional programming)开始获得越来越多的关注。
不仅最古老的函数式语言Lisp重获青春,而且新的函数式语言层出不穷,比如Erlang、clojure、Scala、F#等等。目前最当红的Python、Ruby、Javascript,对函数式编程的支持都很强,就连老牌的面向对象的Java、面向过程的PHP,都忙不迭地加入对匿名函数的支持。越来越多的迹象表明,函数式编程已经不再是学术界的最爱,开始大踏步地在业界投入实用。
也许继"面向对象编程"之后,"函数式编程"会成为下一个编程的主流范式(paradigm)。未来的程序员恐怕或多或少都必须懂一点。
但是,"函数式编程"看上去比较难,缺乏通俗的入门教程,各种介绍文章都充斥着数学符号和专用术语,让人读了如坠云雾。就连最基本的问题"什么是函数式编程",网上都搜不到易懂的回答。
下面是我的"函数式编程"学习笔记,分享出来,与大家一起探讨。内容不涉及数学(我也不懂Lambda Calculus),也不涉及高级特性(比如lazy evaluation和currying),只求尽量简单通俗地整理和表达,我现在所理解的"函数式编程"以及它的意义。
我主要参考了Slava Akhmechet的"Functional Programming For The Rest of Us"。
一、定义
简单说,"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。
它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。举例来说,现在有这样一个数学表达式:
(1 + 2) * 3 - 4
传统的过程式编程,可能这样写:
var a = 1 + 2;
var b = a * 3;
var c = b - 4;
函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样:
var result = subtract(multiply(add(1,2), 3), 4);
这就是函数式编程。
二、特点
函数式编程具有五个鲜明的特点。
1. 函数是"第一等公民"
所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
举例来说,下面代码中的print变量就是一个函数,可以作为另一个函数的参数。
var print = function(i){ console.log(i);};
[1,2,3].forEach(print);
2. 只用"表达式",不用"语句"
"表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。
原因是函数式编程的开发动机,一开始就是为了处理运算(computation),不考虑系统的读写(I/O)。"语句"属于对系统的读写操作,所以就被排斥在外。
当然,实际应用中,不做I/O是不可能的。因此,编程过程中,函数式编程只要求把I/O限制到最小,不要有不必要的读写行为,保持计算过程的单纯性。
3. 没有"副作用"
所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。
函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
4. 不修改状态
上一点已经提到,函数式编程只是返回新的值,不修改系统变量。因此,不修改变量,也是它的一个重要特点。
在其他类型的语言中,变量往往用来保存"状态"(state)。不修改变量,意味着状态不能保存在变量中。函数式编程使用参数保存状态,最好的例子就是递归。下面的代码是一个将字符串逆序排列的函数,它演示了不同的参数如何决定了运算所处的"状态"。
function reverse(string) {
if(string.length == 0) {
return string;
} else {
return reverse(string.substring(1, string.length)) + string.substring(0, 1);
}
}
由于使用了递归,函数式语言的运行速度比较慢,这是它长期不能在业界推广的主要原因。
5. 引用透明
引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。
有了前面的第三点和第四点,这点是很显然的。其他类型的语言,函数的返回值往往与系统状态有关,不同的状态之下,返回值是不一样的。这就叫"引用不透明",很不利于观察和理解程序的行为。
三、意义
函数式编程到底有什么好处,为什么会变得越来越流行?
1. 代码简洁,开发快速
函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。
Paul Graham在《黑客与画家》一书中写道:同样功能的程序,极端情况下,Lisp代码的长度可能是C代码的二十分之一。
如果程序员每天所写的代码行数基本相同,这就意味着,"C语言需要一年时间完成开发某个功能,Lisp语言只需要不到三星期。反过来说,如果某个新功能,Lisp语言完成开发需要三个月,C语言需要写五年。"当然,这样的对比故意夸大了差异,但是"在一个高度竞争的市场中,即使开发速度只相差两三倍,也足以使得你永远处在落后的位置。"
2. 接近自然语言,易于理解
函数式编程的自由度很高,可以写出很接近自然语言的代码。
前文曾经将表达式(1 + 2) * 3 - 4,写成函数式语言:
subtract(multiply(add(1,2), 3), 4)
对它进行变形,不难得到另一种写法:
add(1,2).multiply(3).subtract(4)
这基本就是自然语言的表达了。再看下面的代码,大家应该一眼就能明白它的意思吧:
merge([1,2],[3,4]).sort().search("2")
因此,函数式编程的代码更容易理解。
3. 更方便的代码管理
函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。
4. 易于"并发编程"
函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"(concurrency)。
请看下面的代码:
var s1 = Op1();
var s2 = Op2();
var s3 = concat(s1, s2);
由于s1和s2互不干扰,不会修改变量,谁先执行是无所谓的,所以可以放心地增加线程,把它们分配在两个线程上完成。其他类型的语言就做不到这一点,因为s1可能会修改系统状态,而s2可能会用到这些状态,所以必须保证s2在s1之后运行,自然也就不能部署到其他线程上了。
多核CPU是将来的潮流,所以函数式编程的这个特性非常重要。
5. 代码的热升级
函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。所以,可以在运行状态下直接升级代码,不需要重启,也不需要停机。Erlang语言早就证明了这一点,它是瑞典爱立信公司为了管理电话系统而开发的,电话系统的升级当然是不能停机的。
摘自:http://www.ruanyifeng.com/blog/2012/04/functional_programming.html
分享到:
相关推荐
标题中的“函数式编程初探共2页.pdf.zip”表明这是一个关于函数式编程的压缩文件,其中包含一个两页的PDF文档。函数式编程是一种编程范式,它将计算视为数学函数的求值,强调避免可变状态和副作用,使得程序更加简洁...
他发现图形计算器不仅在函数和统计分析上有强大功能,其独特的编程特性也极具吸引力。通过学习和实践,他掌握了基本的编程命令,并设计出了一些简单但实用的程序。 首先,文章介绍了图形计算器中的一些基本命令,...
【初探Delphi模式编程】 Delphi是一种基于Object Pascal的集成开发环境,它以其高效、易用和强大的组件化编程闻名。模式编程是软件工程中的一种最佳实践,它通过使用预定义的解决方案模板来解决常见设计问题。在...
### 初探uCOS-II:嵌入式操作系统的基础与应用 #### 一、uCOS-II简介 uCOS-II,全称MicroC/OS-II,是一款专门为嵌入式系统设计的操作系统内核。它由Jean J. Labrosse创建,并在开源社区中得到了广泛的认可和支持。...
【初探传说中的setImmediate函数】这篇文章主要探讨了JavaScript中处理单线程执行阻塞问题的方法,特别是如何利用异步调用来改善用户体验。JavaScript的单线程特性意味着长时间运行的操作会阻塞UI更新,影响交互性。...
- Kotlin 支持函数式编程风格,包括 Lambda 表达式、高阶函数等。 - Lambda 表达式的使用可以使代码更加简洁,例如,设置点击监听器的代码如下所示: ```kotlin findViewById(R.id.content).setOnClickListener ...
函数式编程也是Python的一大亮点。高阶函数、闭包、装饰器等概念可以帮助学生开发出更高效、可读性更强的代码。教师可以通过讲解和实践案例,使学生熟悉这些高级特性。 此外,Python在数据处理和分析方面有广泛的...
其实在函数式编程中,函数里面还可以嵌套函数,如下面这样 def foo(): print(hello world in foo) def bar(): print(hello world in bar) 此时我们调用foo函数,执行结果会是什么样子的呢?? hello world in ...
2. 函数式编程元素:支持闭包和块,可以实现简洁的代码结构。 3. 强大的元编程能力:Ruby允许在运行时修改类和对象,增强了灵活性。 **二、Rails框架** 1. MVC架构:Rails将应用程序分为模型(Model)、视图(View...
1. 案例的多样性:确保案例覆盖不同的编程范式和技术,如面向对象编程、函数式编程等,以拓宽学生的视野。 2. 持续评估和反馈:定期检查学生的学习进度,给予及时的反馈,以便调整教学策略,确保学生跟上学习节奏。 ...
8. 数据结构的发展趋势:文章中也可能会提及数据结构未来的发展方向,比如面向对象的数据结构、函数式编程中的数据结构设计等。 由于提供的内容片段信息不完整,无法直接展示文章中的具体内容,但通过以上的知识点...
这包括面向对象编程(OOP)、函数式编程(FP)等,以及设计模式的应用。设计模式是对常见问题的解决方案,如单例模式、工厂模式等,它们可以帮助程序员更高效地解决实际问题。通过深入理解这些编程范式,可以提高...
"初中Python程序设计教学方法初探"的主题,旨在探讨如何有效地将Python编程引入中学教育,帮助学生建立起编程思维,培养他们的逻辑分析能力和创新能力。 Python教学在初中阶段的实施,首先需要关注的是教学内容的...
前言 本文受启发于Trisha Gee在JavaOne 2016的主题演讲Refactoring to ...不要怕,具体到语言层面上Lambda表达式不过是一种新的语法而已,有了它,Java将开启函数式编程的大门。 为什么需要Lambda表达式 不
关键字是编程的基础,运算符的理解需结合数学知识,而常量库函数如`scanf()`和`printf()`则是初学者必须掌握的输入输出函数。 - 程序基本结构的学习不容忽视。良好的编程习惯始于理解程序的结构,如顺序结构、选择...
2.1.2 Scala:Scala是一种多范式编程语言,它集成了面向对象和函数式编程的特点。虽然使用Scala开发Android应用相对较少,但它提供了强大的语言特性和表达能力。 2.1.3 Java:Java是Android开发最常用的语言,...
【中职Python语言教学方法初探】 在职业教育中,Python语言的教学显得尤为重要,尤其是在计算机及网络信息安全等相关专业中。Python以其简洁的语法和强大的功能,成为初学者学习编程的首选语言。然而,如何提高中职...
这篇"ASP初探(为了忘却而去纪念)"很可能是为那些想要入门ASP编程的新手准备的教程,旨在提供一个基础的、详细的指南。 在ASP中,开发者可以使用VBScript或JScript等脚本语言编写代码,这些代码在服务器上执行后...
教学内容应紧密围绕计算机等级考试的大纲,确保学生掌握考试所要求的基本语法、数据结构、函数、模块化编程、异常处理等关键知识点。同时,还要涉及面向对象编程、文件操作、网络编程等进阶主题,以应对可能出现的...