为什么函数式编程重要

对于函数式编程来说,1989/1990 是一个相当黑暗的年代。面向对象程序设计日益突出的表现,使得工业界对于函数式编程的关注越来越少。随着 John Hughes 的一篇论文"为什么函数式编程如此重要"("Why Functional Programming Matters")的发布,它强力论证了主流思想在忽视函数式编程中可能犯的错误。这几乎是你唯一能听到的声音了。

本文试图向大多数非函数式程序员去论述函数式编程的意义,并同时帮助函数式程序员发现其优势并加以利用。

我引用的那篇论文总共有23页,其中大部分的内容都是举例阐述作者的观点。它的中心论点相当简洁,讨论激烈,并且与我们上周讨论的主题有关,这就是 模块化 。原谅文章开头部分的大量引用,这是因为 Hughes 的文章写的太好以致于不能省略。

现在人们普遍认为模块化设计是编程的关键。但是,有个非常重要的点容易被忽视。当用模块化编程去解决问题,需要把一个问题分解为子问题,解决所有子问题后再合并结果。分解原问题的方式直接取决于如何合并结果。因此,为了从概念上提升模块化能力,在编程语言中需要提供新的胶水能力( glue )。

但我们一直在进步,Hughes 认为绝大多数人谈论到函数式编程的优势时,会经常讨论到:无副作用(side-effect free) 和 不包含赋值语句 (contain no assignment statements)。因此,表达式可以在任何时候计算并替换为其值, 这样程序就是引用透明的(referentially transparent)。即使到了今天也经常讨论计算时值不可变(value of immutability)无副作用(side-effect free),当然,这些很有价值。但对于不熟悉的人来说,并不是很好的方式去解释函数式编程(FP)。

例如范畴论的"优势"非常明显,但如果其他人不认真了解它,就不会对此感到惊讶。它说了许多函数式编程没有的内容(没有赋值,没有副作用,没有控制流),但是并没有说它的内容是什么。函数式编程听起来就像严守清规戒律的僧徒,牺牲了生活中的乐趣来希望自己变得纯粹。对于物质利益更感兴趣的人来说,这些优势完全没有说服力。

使用函数式编程程序员会说,函数式编程是数量级更轻的一种,因此开发人员效率更高。

但为什么会是这样?唯一可能的理由是传统编程中大概有90%的代码是赋值语句,这就是函数式编程优势的基础。在函数式编程中,赋值语句可以省略,这显然很荒谬。如果省略赋值语句带来了如此巨大好处,那么 FORTRAN 程序员可能20年来都这样做了。

如果函数式编程这些特性不能够说服你,那么什么内容能既诠释函数式编程的威力,又指明函数式程序员所追求的方向呢?想一下结构化程序设计出现的年代,Hughes 总结它的优势可能可以归为一句话:结构化程序设计不包括 goto 语句。这与函数式编程所提的负面优势情形一样。

事后来看,结构化程序设计这些特性虽然有用,但并没有触及到问题的核心。结构化与非结构化程序设计最重要的不同之处是: 结构化程序设计是一种模块化设计方式。模块化设计带来了极大生产力的提升:

  • 小模块可以快速方便的编写。
  • 通用模块可以复用,加快后续开发进度。
  • 模块化编程能够被单元测试,帮助减少debug时间。

缺少goto语句有助于"小范围"编程,模块化则有利用"大范围"编程。现在我们回到最初提的问题:分解问题的方式直接依赖于胶水粘合解决结果的方式。

接下来我们会讲述函数式语言提供的两种新的,很重要的胶水能力。这是函数式编程能力的关键-它提高了模块化能力。它也是函数式程序员必须实现的目标 - 更小更简单更通用的模块,通过新的胶水能力粘合在一起。

两个新的胶水能力是

  • 高阶函数
  • 惰性求值

**高阶函数(Higher-order functions)**能够使简单函数粘合成更复杂的函数。我想大多数读者都熟悉这个想法。论文中的例子是foldr,它在列表( list )上抽象了一个通用的计算模式,例如以下几个例子:

sum = foldr (+) 0
product = foldr (*) 1
anytrue = foldr or False
alltrue = foldr and True
length = foldr count 0 // where count a n = n + 1
map f = foldr (Cons .f) Nil
summatrix = sum . map sum

更多的例子。

这些例子总以让读者信服一点:模块化可以走的更远。将简单函数( sum )模块化为高阶函数和一些简单参数的组合,就得到高阶函数(foldr),可以用来编写列表中其他函数而不需要额外开发。

这不仅仅只适用于列表( list ), 你可以为任何数据结构编写高阶函数。

所有这些都可以实现,因为函数在传统编程中不可分割,在函数式编程中能够表示为高价函数和特殊函数的组合。一旦定义好,高阶函数使一些操作更容易实现。无论何时定义一个新的数据类型,应该编写高阶函数处理它。这使得操作数据类型变得容易,并且将细节知识进行本地化表示。

**惰性求值( Lazy evaluation)**需要更多的思考去了解为什么 Hughes 将它归为模块化的机制: 惰性求值使得模块化程序做为生成器成为现实,并且能构造大量可能的答案,选择器会选择合适的一个。没有惰性求值这些不会被实现。(如果可能,那要在有无限生成器的情况下)

我们已经在函数语言上下文中描述了惰性求值,但是如此有用的特性应该加到非函数式语言中。惰性求值和副作用会共存么?不幸的是他们不能:在命令式符号中增加惰性求值是可能的,但是这种结合会让程序员的工程更加困难。

对于那些喜欢混合函数和非函数构造的语言,需要考虑一些问题。

为什么它会让程序员的工作更困难呢?

因为惰性求值的威力需要程序员放弃程序执行时各部分之间顺序执行的控制能力,这会使带有副作用的编程变得困难,因为预测它们的发生顺序和是否发生,需要足够了解它们内嵌的上下文信息。这种全局依赖性将会破坏函数式语言中的模块性。惰性求值是为改善这种情况设计的。

下面一系列例子来证明惰性求值和高阶函数的威力: 牛顿-拉佛森平方根法; 数值微分与积分;评估博弈树的 alpha-beta 启发式算法。在所有的例子中,它表现出了如何迅速达到强大和富有表现力的抽象层次,令人印象深刻,值得我们仔细学习。

Why Functional Programming Matters John Hughes, Research Topics in Functional Programming, 1990 (based on an earlier Computer Journal paper that appeared in 1989).

转载于:https://juejin.im/post/5c9f27706fb9a05e5716324a

你可能感兴趣的:(测试,数据结构与算法)