# 零、简介
我的管理层总是认为我的程序昨天必须是函数式的。加里·惠勒
## 关于这本书
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#)中,有几种不同的方法来处理函数式编程。