# 零、简介 我的管理层总是认为我的程序昨天必须是函数式的。加里·惠勒 ## 关于这本书 F#不仅仅是另一种编程语言。它主要是为函数式编程而设计的,这既要求也要求对计算机程序有不同的思考方式。函数式编程基于一个非常简单的原则,即行为由输入严格决定,因此相同的输入总是产生相同的行为。函数的行为类似于高中代数中的函数,但可能在包括其他函数和计算在内的多种结构化数据类型下运行,而不仅仅是数字。这个简单的想法对编程实践有许多影响。例如,在命令式语言中,我们很少将函数传递给其他函数,我们经常使用表现出(有意或无意)副作用的可变对象。当使用函数式编程语言时,情况正好相反:函数通常作为参数传递给其他函数,对象是不可变的,除非有意实现,否则没有副作用。 这本书探讨了“如何进行函数式思考”,这与强制性思考非常不同。函数式编程需要考虑计算机程序中类似代数的计算,这些计算是从简单的类似代数的计算中建立起来的。仅仅学习 F#语法是不够的。因此,我将很少花时间讨论 F#语言本身——学习 F#语法的资源有很多,例如 *F#简洁地*[【1】](IFP_0010.htm#_ftn1)和 Wikibooks 的 *F#编程*。[【2】](IFP_0010.htm#_ftn2)如果你对 F#完全陌生,我强烈建议你先通读那两个资源。 相反,本书将讨论函数式编程概念,例如: * 携带 * 部分应用 * 不变 * 一流的功能 * 高阶函数 * 功能管道 * 功能组合 * 递归 * 映射、过滤和缩小 * 延续 * 延续传球风格 * 单子(计算表达式) 理解这些概念以及它们如何改变你对编程的思考方式,对于成为一名熟练的函数式程序员至关重要。 即使您没有立即计划使用函数式编程语言,这些概念中的许多正在进入命令式语言,如 C#和 C++。作为一名命令式语言程序员,熟悉这些概念也会加强你的命令式编程技能。 本书分为四章: * 第 1 章–基本词汇和概念 * 第二章——改变你的思维 * 第 3 章–深入 * 第 4 章–命令/功能交互 目的是以特定的顺序和合理的速度介绍概念,这样,当我们到达[第 3 章](3.html#_Chapter_3_)时,您将很好地理解如何处理 F #-特定的概念。 ## 你应该已经知道的 您应该已经接触过 F#或其他一些支持函数式编程的语言,比如 Python、Haskell 或 Clojure,仅举三个例子。一些例子将 F#与 C#进行比较,因此建议掌握大量的 C#知识,尤其是 C# 3.0 及更高版本。 ## 使用的资源 本书中的代码示例是用 Visual Studio 2012 编写的,也使用了 Syncfusion Essential Studio 和 Microsoft SQL Server Express。特别是[第 2 章](2.html#_Chapter_2_)和[第 3 章](3.html#_Chapter_3_)中的许多代码示例需要微软的示例数据库 AdventureWorks。AdventureWorks 的正确版本取决于 SQL Server 的版本;请查阅 AdventureWorks 数据库的 Microsoft 文档,以确定数据库的正确版本。如果您没有安装 SQL Server,可以从微软下载免费版的 SQL Server Express 来完成演示。 ## 函数式编程的历史 函数式编程的根源在于 lambda 演算,lambda 演算是“数学逻辑和计算机科学中的一种形式系统,用于表达基于函数抽象的计算以及使用变量绑定和替换的应用。”[【3】](IFP_0010.htm#_ftn3)λ演算是由数学家阿隆佐·邱奇[【4】](IFP_0010.htm#_ftn4)在 20 世纪 30 年代引入的。 就我们的目的而言,lambda 演算的显著特点是它使函数成为一级对象,也就是说它们可以以与正则值或对象相同的方式使用。一级函数的概念在 20 世纪 60 年代由克里斯托弗·斯特雷奇(Christopher Strachey)明确提出,他还创造了“T4”一词,这是函数编程的一个基本概念。顺便说一句,术语 *currying* 有时也被称为*schnfinkling,*以摩西·施芬克尔[【7】](IFP_0010.htm#_ftn7)命名,用于起源“……转换一个包含多个参数(或一组参数)的函数的技术,使其可以被称为一个函数链,每个函数只有一个参数……”。[【8】](IFP_0010.htm#_ftn8)我们常用 *currying* 这个术语,以哈斯克尔·库里[【9】](IFP_0010.htm#_ftn9)命名,他在 Schö nfinkel 论文的基础上进一步发展了组合逻辑的概念。 最早具有功能特征的语言之一是 1958 年由约翰·麦卡锡开发的 Lisp、[【10】](IFP_0010.htm#_ftn10)。[【11】](IFP_0010.htm#_ftn11)有趣的是,Lisp 开创了许多我们在现代编程语言中认为理所当然的想法:树数据结构、动态类型、条件句和递归等等。 主流语言已经慢慢采用了对函数式编程的支持。Python,[【12】](IFP_0010.htm#_ftn12)在 1994 年,增加了对 lambda 表达式和集合操作过滤、映射和减少(也称为“折叠”)的支持。2007 年 C# 3.0 引入了一流的函数。 ## c#中的λ表达式 我们看到“lambda expression”这个术语在许多最新的编程语言中使用,包括 C#。[【13】](IFP_0010.htm#_ftn13)在 C#中,lambda 表达式是匿名函数,常用来编写 LINQ 查询表达式。例如,C#的集合对象提供选择器函数,您可以使用这些函数来选择集合的特定元素: ```fs int[] numbers = { 1, 2, 3, 4, 5 }; int sumOfOddNumbers = numbers.Sum(x => x % 2 == 1 ? x : 0); Console.WriteLine("Sum of odd numbers 1..5: " + sumOfOddNumbers); ``` lambda 表达式: ```fs x => x % 2 == 1 ? x : 0 ``` 如果 x 为奇数,则返回`x`;否则返回 0。 ## c#中的函数式编程 如果你曾经使用过 C#的`Action`或`Func`委托,那么你已经在用函数式编程风格编写了(从技术上讲,如果你曾经使用过委托和事件,那么你已经在做一些函数式编程了)。 例如,您可以将一个方法指定为`Func`(只要它匹配预期的返回类型和参数类型),并将其用作一个函数来限定另一个函数的操作。在下面的例子中,我们指定了一个方法(一个取`int`并返回`int`的函数)来过滤我们想要求和的数组中的值: ```fs static int EvenOrZero(int x) { return x % 2 == 0 ? x : 0; } int[] numbers = { 1, 2, 3, 4, 5 }; int sumOfEvenNumbers = numbers.Sum((Func)EvenOrZero); ``` 在本例中,强制转换对于解决`Func`和`Func`之间的歧义是必要的。 或者,您可以使用 lambda 表达式实例化函数(这消除了强制转换的问题): ```fs Func evenOrZero = x => x % 2 == 0 ? x : 0; int[] numbers = { 1, 2, 3, 4, 5 }; int sumOfEvenNumbers2 = numbers.Sum(evenOrZero); ``` 在这两种情况下,`Action`和`Func`委托及其变体的使用利用了 C#的委托能力,使函数成为一级公民,由此也可以组成更高阶的函数。 ## LINQ 与函数编程 在前面的例子中,我们真正做的是使用 LINQ (Language INtegrated Query)的方法语法,并提供一个谓词或一个 lambda 表达式。您通常会得到 LINQ 语法和 lambda 表达式的混合,所以当使用命令式语言(如 C#和 LINQ 和 lambda 表达式)时,线条会变得模糊。一个纯粹的 LINQ 例子如下: ```fs int[] numbers = { 1, 2, 3, 4, 5 }; int sumOfOdds = (from s in numbers where s % 2 == 1 select s).Sum(); ``` 事实上: * `LambdaExpression`类是`System.Linq.Expressions.LambdaExpressions`命名空间的成员。 * 不包含`System.Linq`就不能使用`Sum`等聚合功能。 如前面的例子所示,在命令式语言(如 C#)中,有几种不同的方法来处理函数式编程。