函数式编程导论——引论1

1.1  函数式编程的优点


乍看上去,一个没有变量或顺序结构的编程语言似乎完全是不切实际的。此时,这种别扭的印象不是那种通过简单的短短数语就可以消除的。但是我们希望读者通过学习下面的内容后能对函数式编程语言获得一种新的认识:以函数的方式可以进行许多有趣的编程。


命令式编程没有什么“神圣”之处,只不过是我们相对比较熟悉它罢了。命令式编程的许多特性以前通过一种抽象过程被提出来的,即:从典型的计算机硬件和机器码到汇编器,再到宏汇编器,然后Fortran语言,最后到现在的样子。毫无疑问这些命令式编程模式代表着人类用程序与机器交流的最爽的方式。然终究,已经存在的硬件设计也非神圣不可侵犯的,并且计算机应该执行我们的指令而不是执行我们的逆指令。也许正确的设计方法不是从硬件开始自底向上设计,而是从描述算法的抽象符号集合的编程语言开始,然后自顶向下的设计。实际上,在传统编程语言中也会看到这种趋势。甚至在Fortran语言中就容许以通常的方式来在程序中写算术类表达式。程序员也从大量的线性化子表达式求值任务和为中间结果寻找临时存储空间的劳动中被解放了出来。


这表明此种与传统的命令式语言的开发思路完全不同的设计思想是确实站得住脚的。然而,想强调的是,我们这样做不是哗众取宠式地为了改变而改变,为此接下来我们给出了解释——为什么比起命令式程序我们更喜欢函数式程序?


也许主要的原因在于函数式程序更直接地符合数学对象,因此也会更容易地对其进行推导。为了更好地把握程序的确切含义,我们希望给一个程序或指令赋予一种抽象的数学含义——以此达到指派语义的目的。在命令式语言中,由于状态与值的隐含依赖,使得其语义指派必须以一种相当间接的方式完成。在简单的命令式语言中,可以将一个命令与一个函数:Σ —> Σ 相关联,其中Σ 表示程序某个状态所有可能具有的值的集合。换句话说就是,一个程序指令接受一些状态,当指令执行完毕后会产生另一些状态。程序可能会陷入死循环(如:while true do x := x),那么这类函数通常是局部的。有时会优先选择可替换的语义,如:在(Dijkstra 1976)中提到的谓语替换器。但是如果我们增加一些更加复杂的妨碍程序执行顺序的方法,如:goto语句,或C语言中的break和continue语句,甚至这些个不再起作用的解释,那么由此一个命令会造成其后面的一些命令被跳过。与此相反的具有代表性的方式就是基于延续性使用更复杂的语义。


相比之下,用Henson的话说,函数式编程是将它们的语义穿在了它们的袖子上。我们可以通过使用ML语言来对此举例说明。函数式编程语言里的基本数据类型都可以直接被解释为数学对象。如使用标准符号[[X]]来表示:X的语义,那么我们可以举例说 [[int]] = Z,即:[[int]]表示的含义是数学上的所有整数对象。现在ML函数fact被定义下面的样子:

let rec fact n =

if n=0 then 1

else n * fact(n-1);;

可以看到该函数有一个类型为int的参数,并且返回类型为int的一个值,由此它可以被简单地与一个抽象的局部函数(Z ——> Z)关联起来,进而该函数可以被记为如下形式:

                                              

(这里,符号⊥ 表示未定义,因为对于值为负数的参数,程序是不会成功执行完毕的。)然而,这种简单在非函数式程序中是讲不通的,因为其中所谓的“函数”从数学意义上来讲是不能被称为函数的。例如:在C标准库中的一个功能函数rand()——在接连地被调用后会返回不同的伪随机数。然这种事情通过使用一个局部静态变量来存储先前的结果也可以被实现,如:

int rand(void)

{ static int n = 0;

  return n = 2147001325 * n + 715136305;

}


因此,可以发现在放弃类似goto机制以后,对变量和赋值类操作的放弃会成为符合逻辑的下一阶段选择。一个更简单的语义学会使得关于程序的推理更加直接。这也对程序正确性证明和向更高效程序的正确转换领域开辟了更多的可能性。


下面介绍函数式语言另一个潜在的优势,即:由于表达式的求值在程序的任何状态都不会产生副作用,使得不同的子表达式可以以任何顺序进行求值同时不会相互受到影响。这意味着函数式程序会很好地适用于并行实现,如:计算机可以自动地将不同的子表达式移交给不同的处理器处理。相反,命令式编程常常利用一种相当严格的执行顺序,而且即使在现代的流水线处理器中有限的指令交织也被证明是复杂的同时充满技术问题的。


实际上,ML语言不是一个纯函数式编程语言;如果需要,它也支持变量和赋值。大部分时间,我们都工作在纯粹地函数的子集中。但是即使我们使用赋值操作,进而失去一些先前提到的函数式编程的优点,也会在更加灵活使用函数式机制方面(如:ML中所提供的)存在不少优势。使用高阶函数(一些函数作用到其它函数上),常常可以写出具有非常简洁和高雅的形式的程序。代码描述变得更一般化,因此即使作用到其它函数上它也可以被参数化。例如,有一段计算数字列表元素之和的程序,以及一段计算数字列表元素之积的程序可以被看做是相同程序的不同实例,即:通过成对的算术操作符和相应的身份对象将该同一程序参数化为不同的实例。例如:给该同一程序传递参数(+ ,0)或者(* , 1)。最后,函数也可以被用来以一种方便的方式表示无穷的数据——例如:在后面我们将展示怎样使用函数来执行实数的精确计算,这有别于浮点的近似。


然而,函数式程序并不是没有自身的问题的。由于它们与硬件中的最终执行过程不是直接对应的,所以很难准确地估算出函数式程序的资源使用率,如:运行时间和空间占用。虽然基于无限序列有巧妙的技术,但输入输出也很难被灵活地并入一个函数模型。这取决于读者们读完本书后是否对函数式风格优点认同。我们不希望去强加任何意识形态,仅仅是去指出看待编程有许多不同的方式以及在适当的情景中,函数式编程可能会有相当大的优势。大部分我们举出的实例都是从哪些被轻率地描述为“符号计算”的领域中挑选的,因为我们相信函数式编程在这种应用中会很好地工作。然而,一个人总会找到最适合某项工作的工具。命令式编程、面向对象编程或逻辑编程也许更加适合某些其它任务。各有所长吧。


1.2 概述

对那些过去常常采用命令式编程的程序猿们,无论采用何处方式转向函数式编程,都不可避免会遇到很多困难。尽管一些人着急着想快速地开始真正的编程,但是我们还是选择从λ演算开始,同时讲解λ演算时如何被看作是函数式语言的理论基础的。这样可以了解实际的开发历史线条。


由此我们首先介绍λ演算,并且解释最初打算被作为形式化逻辑系统的数学如何被证明是一个完整通用的编程语言。然后讨论我们为什么想要为λ演算添加类型,以及如何完成这一工作的。添加类型这项工作会将我们带入ML领域——它本质上是一个拥有某种求值策略的有类型λ演算的扩展和优化的实现。我们在本书中会涉及到用ML语言进行基本函数编程的实践,并且会讨论多态性和大多数一般的类型。接着我们会移步到更加高级的话题,包括:异常和ML语言的命令式特征。最后,我们提供一些重要的例子,以此证明ML的实力。


进一步阅读:略。


本人水平有限,翻译不周到之处,请读者指正,谢谢!

英文原版链接:http://www.cl.cam.ac.uk/teaching/Lectures/funprog-jrh-1996/




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值