函数式编程(Functional Programming,FP)已成为主流编程中一个重要且令人兴奋的组成部分。在2010年代创建的大多数新语言和框架都是函数式的,这导致一些人预测编程的未来是函数式的。与此同时,流行的面向对象(Object-Oriented,OO)语言,如C#和Java,在每个新版本中都引入了更多的函数式特性,支持多范式编程风格。然而,C#社区的采用速度很慢。为什么会这样呢?我认为,其中一个原因是缺乏优秀的文献:
l 大多数FP文献都是用函数式语言编写的,特别是Haskell。对于具有OOP背景的开发人员来说,这对学习FP概念造成了编程语言上的障碍。尽管许多概念适用于像C#这样的多范式语言,但同时学习一门新的范式和一门新的语言是一项艰巨的任务。
l 更重要的是,文献中的大多数书籍倾向于用数学或计算机科学领域的例子来说明函数式技术和概念。对于大多数终日从事业务(Line of Business,LOB)应用程序开发的程序员来说,这造成了一个领域空白,并使得他们难以知悉这些技术与实际应用程序间的相关性。
这些缺陷成为我学习FP道路上的主要绊脚石。许多书试图解释什么是“柯里化”,通过用数字3创建一个可以将3加到任何数字上的函数,以展示add函数是如何被柯里化的(在你能想到的所有应用中,它有一点实际用处吗?)。抛弃这些书后,我决定开辟自己的研究路径。这需要学习6种函数式语言,并通过实验找出其中哪些FP概念可以有效地应用于C#中,并应用于大多数开发人员有偿编写的应用程序中。我的研究成果最终通过本书得以展现。
本书不仅展示如何在C#语言中利用函数式技术,为C#开发人员弥合语言鸿沟,还展示如何将函数式技术应用于典型的业务场景来弥合领域差距。我采用了一种实用的方法,并在一定程度上涵盖了函数式技术,使它们在典型的LOB应用程序场景中有用,并省去了FP背后的大部分理论。毕竟,我们关注FP,是因为它具有以下优点:
l 强大——这意味着可以用更少的代码完成更多的工作。FP提高了抽象级别,允许编写高级代码,同时将程序员从增加复杂性但没有价值的低级技术问题中解放出来。
l 安全——FP反对状态突变。这意味着用函数式风格编写的程序不太可能进入无效状态。此外,在处理并发性时,状态变化的保守方法是非常有益的。用命令式风格编写的程序在单线程实现中可能工作得很好,但当出现并发性时,就会导致各种各样的缺陷(bug)。函数式代码在并发场景中提供了更好的保证,所以在多核处理器时代,对FP的兴趣激增是很自然的。
l 清晰——相对于编写新代码,我们会花费更多的时间来维护和使用现有的代码,所以代码清晰且意义明确是很重要的。当你学会函数式思维时,就能够轻松编写这种清晰的代码。
如果你已经用OO风格编程一段时间了,那么在本书中的概念实现之前,可能需要一点努力和意愿来进行实验。为了确保学习FP是一个愉快和有益的过程,这里有两个建议:
l 保持耐心。有些部分可能要多次阅读。也可能把这本书放下几个星期后,当你再次拿起它时,一些看似模糊的东西突然开始变得明朗起来。
l 实验代码。实践出真知。本书提供了许多示例和练习,许多代码片段可以在REPL中进行测试。
你的同事可能没有你那么热衷于探索。由于预料到他们可能抗议你采用这种新的编码风格,并困惑地看着代码,说:“为什么不只是做x呢?”(其中x是枯燥的、过时的,而且通常是有害的)。此时,请不要过多地和他们讨论。只需要坐下来等待,等他们碰壁而归时,用你的技巧解决他们屡次遇到的问题。
关于本书
本书旨在展示如何利用C#中的函数技术来编写简洁、优雅、健壮和可维护的代码。
本书读者对象
本书是为雄心勃勃的开发人员编写的。你需要知道.NET和C#或者类似的语言,如Java、Swift或Kotlin;需要具有开发实际应用程序的经验;需要熟悉OOP概念、模式和最佳实践;并且希望通过学习函数技术来扩展知识库,以最大限度地将C#用作一种多范式语言。
如果你正在尝试或计划学习一门函数式语言,那么本书将非常有价值,因为本书将教会你用熟悉的语言进行函数式思考。改变思维方式是很难的,而一旦做到这一点,学习任何一种特定语言的语法都会相对容易。
本书结构:路线图
全书内容共有19章,分为以下4部分:
l 第Ⅰ部分介绍了函数式编程的基本原理。首先讲解什么是函数式编程,以及C#如何支持函数式编程。然后讨论高阶函数的幂和纯函数的重要性。读完第Ⅰ部分后,你将获得有用的概念性和实用性工具以学习更具体的函数式技术。
l 第Ⅱ部分展示了函数技术的一些实际应用:如何设计类型和函数签名,以及如何将简单的函数组合到复杂的程序中。读完第Ⅱ部分后,你会明白用函数式风格编写的程序是什么样的,以及这种风格所带来的好处。
l 有了这些基本概念,第Ⅲ部分将加快讲解速度,并继续关注更广泛的问题,如函数的错误处理,模块化和组合应用程序,以及理解状态和表示变化的函数式方法。读完第Ⅲ部分后,你将获得一系列工具,以使用函数式方法有效地处理许多编程任务。
l 第Ⅳ部分讨论了更高级的主题,包括惰性计算、有状态计算、异步、数据流和并发。第Ⅳ部分的每一章均介绍了一些重要的技术,这些技术有可能完全改变你编写和思考软件的方式。
你会在每一章中找到更详细的主题分类,并在阅读任何特定章节之前,都能从本书的封二了解到需要预先阅读哪些章节。
为实际应用程序编写代码
本书的目标是保持真实的现实场景。为此,许多示例都涉及实际任务,如读取配置、连接到数据库、验证HTTP请求等。你可能已经知道如何完成这些任务,但本书将带你从函数式思维的新角度处理这些任务。
本书使用了一个长时间运行的示例来说明FP如何在编写LOB应用程序时提供帮助。为此,我为虚构的Bank of Codeland (BOC)选择了一个在线银行应用程序,我知道这很无聊,但至少它有了必需的三个字母的缩写。因为大多数人都可以使用在线银行工具,所以很容易想象其所需的功能,并明白所讨论的问题与现实应用程序的相关性。
本书使用其他几个场景演示如何以函数式风格解决典型的编程问题。在实际例子和FP概念之间来回转换是为了弥合理论和实践之间的差距,而这是现有其他文献所缺乏的。
利用函数库
像C#这样的语言可能包含一些函数特性,但是为了充分利用这些特性,通常会使用一些库来促进常见任务的完成。这些库包括:
l System.Linq——这是一个函数库。我假设你是熟悉它的,因为它是.NET的一个重要组成部分。
l System.Collections.Immutable——这是一个不可变集合库,第11章将开始使用它。
l System.Interactive和System.Reactive——这些库(它们是.NET的交互扩展和响应式扩展)允许处理数据流,详见第16章和第18章。
还有很多组成FP的其他重要类型和函数。因此,一些独立的开发人员编写了一些库来填补这些空白。到目前为止,其中最完整的是language-ext,这是由Paul Louth编写的库,用于改善C#开发人员的函数式编程体验。
本书没有直接使用language-ext,而是向你展示我开发函数实用程序库(名为LaYumba.Functional)的实际过程,尽管它在很大程度上与language-ext重叠。这在教学上更有用,原因如下:
l 在本书出版后,代码将保持稳定。
l 你可以透过现象看本质,看到功能强大的函数结构其实很容易定义。
l 可以专注于基本要素。我以最纯粹的形式展示这些结构,这样你就不会被完整的库所解决的细节和边缘情况分散注意力。
关于代码
这是《C#函数式编程》的第2版,使用了C# 10和.NET 6 。许多(如果不是全部)技术可以应用于该语言的以前版本,但这样做通常需要一些额外的输入。附录中专门说明了如何使用不可变数据和模式匹配(如果使用的是早期版本的C#),而这些语言特性不包括在本书中。
可扫描本书封底的二维码,下载本书的源代码及相关资源。要说明的是,读者在阅读本书时会看到一些有关链接的编号。形式是数字编码加方括号,例如[1]表示读者可扫描封底二维码下载Links文件,并找到对应章节中[1]所指向的链接。
本书中的代码清单重点关注的是正在讨论的主题,因此可能会省略using语句、名称空间声明、简单的构造函数,或先前代码清单中出现并保持不变的代码段。如果你想查看代码清单的完整编译版本,可以在代码存储库中找到它。