F# 简介

概述

F# 是微软公司推出的一种新的功能性编程语言。尽管它主要是一种功能性编程语言,但它却因多范式语言而知名,因为它还支持面向对象编程。F# 与整个 .NET 库系统和开发环境紧密集成,因此开发人员能够将功能性编程功能引入 .NET 平台。

目标

在本次动手实验中,您将学习如何:

•              使用 F# 类型

•              利用 let 关键字绑定值与标识符

•              使用 F# 中的函数

•              使用 F# 中的列表

•              利用模式匹配 (Pattern Matching) 和递归 (Recursion)

•              创建您自己的类型并使用可辨识联合 (discriminated union)

               

系统要求

您必须拥有以下工具才能完成本实验:

•             Microsoft Visual Studio 2010

               

安装

使用 Configuration Wizard 验证本实验的所有先决条件。要确保正确配置所有内容,请按照以下步骤进行。

注意: 要执行安装步骤,您需要使用管理员权限在命令行窗口中运行脚本。

1.            如果之前没有执行,运行 Training Kit 的 Configuration Wizard。为此,运行位于 %TrainingKitInstallationFolder%\Labs\IntroToFSharp\Setup 文件夹下的 CheckDependencies.cmd 脚本。安装先决条件中没有安装的软件(如有必要请重新扫描),并完成向导。

注意: 为了方便,本实验中管理的许多代码都可用于 Visual Studio 代码片段。CheckDependencies.cmd 文件启动 Visual Studio 安装程序文件安装该代码片段。

               

练习

本次动手实验由以下练习组成:

•              掌握基本的 F# 类型,包括元组和函数

•              了解 let 关键字如何将值绑定到标识符

•              了解在 F# 中,函数与其他值一样,能以相同的方式处理。演示这如何支持高级语言功能,比如部分应用的函数(也称为“扩充”函数)。

•              了解如何构建 F# 列表,并研究可以通过 F# 的“Head + Tail”方法实现的功能

•              演示 F# 强大的模式匹配和递归功能

•              演示 F# 中可辨识联合的用途

               

完成本实验的估计时间:60 分钟。

               

后续步骤

练习 1:F# 中的类型

               

练习 1:F# 中的类型

在本练习中,您将了解 Visual Studio 2010 中构建的 F# 交互控制台。您还将了解如何利用 F# 的类型推理,同时还将了解“元组”的概念。

任务 1 –了解 F# 中的类型推理

在本任务中,您将使用 F# 交互控制台查看 F# 是否能通过所使用的上下文推理数据类型。

F# 是一种静态类型语言,广泛使用了类型推理。如果提供基元类型(int、float、string 等),它能够推理出标识符引用的类型。

1.            选择 Start | All Programs | Microsoft Visual Studio 2010 | Visual Studio Tools | Visual Studio 2010 Command Prompt 打开 Visual Studio 2010 Command Prompt

2.            通过启动 fsi.exe 启动 F# 界面。

 

图 1

F# 交互控制台

3.            向 F# 交互控制台输入以下命令:

F#

42;;

Response

val it :int = 42

注意: F# 控制台通过确定值类型并将其绑定到名为“it”的标识符进行求值。

4.            向 F# 交互控制台输入以下命令并按回车键:

F#

it;;

Response

val it :int = 42

注意: 如果不存在标识符,F# 环境将值绑定到名为“it”的标识符。该标识符可用于同一个交互会话的其他代码。

5.            在 F# 交互会话的命令提示符中,输入以下命令并按回车键:

F#

42.0;;

Response

val it :float = 42.0

注意: 通过向值添加小数,F# 可以推理它为浮点类型。

6.            在 F# 交互会话的命令提示符中,输入以下命令并按回车键:

F#

"42";;

Response

val it :string = "42"

注意:通过将我们的值使用双引号括起来,F# 能够推理出该值为字符串。

               

任务 2 –处理元组

在本任务中,您将了解如何在 F# 中处理元组。元组是 F# 中的结构,使开发人员能确保两部分或更多信息总是能一起展示。这在对分散数据(即交易量、交易日期和交易 ID 号)不敏感的情况下非常有用。

注意: 在 F# 中,元组就是组合到一起的几个值。元组是 F# 中的基本类型,可以在能够使用任何其他值的地方使用。

1.            在 F# 交互命令提示符中,输入以下命令并按回车键:

F#

(42, "Hello F#");;

Response

val it :int * string = (42, "Hello F#")

注意: F# 将该值推理为元组。在本例中,元组包括一个整数值和一个字符串值。解释器中显示的类型描述 (“int * string”) 指示该元组中有两个值,第一个值是整数,第二个值是字符串。

2.            元组并不仅限于值对;它们可以包含任意多的值。向 F# 交互控制台输入以下命令并按回车键:

F#

(42, "Hello F#", 42.0);;

Response

val it :int * string * float = (42, "Hello F#", 42.0)

注意: 在本例中,创建的元组 的类型为 “int * string * float”,或者是一个分别使用整数、字符串和浮点值的结构。

               

任务 3 –将函数用作值

在本任务中,您将发现 F# 中的函数就像任何其他值一样,可以用相同的方式处理。

注意: 在 F# 中,值和函数没有区别,因为函数本身就是值。它们都是一类居民。因此,函数可以像其他任何值一样绑定到标识符。

1.            向 F# 交互命令提示符输入以下命令并按回车键:

F#

fun x -> x + 1;;

Response

val it :int -> int = <fun:clo@0>

2.            最后一步创建的函数被绑定到“it”标识符,因此它可以通过该标识符进行访问。在 F# 交互命令提示符处输入以下命令:

F#

it 41;;

Response

val it :int = 42

注意: F# 访问绑定到“it”标识符的函数并执行该函数。但 F# 如何知道这是一个函数调用?传统的面向对象语言使用括号和逗号表示函数调用(即“MyFunction (firstParam, secondParam)”),F# 知道第一个标识符是函数类型(因为函数是 F# 中的一类居民),并预期所有的函数调用参数都使用空格分隔(即“MyFunction firstParam secondParam”)。

因此,F# 代码“it 41”将通知 F# 调用绑定到标识符 it 的函数,并传递 41 作为第一个参数。

3.            副作用是,函数返回的值本身也绑定到“it”。在命令提示符中,输入以下命令并按回车键:

F#

it;;

Response

val it :int = 42

注意: 在本例中,您可以看到返回值 42 已经绑定到“it”标识符。这意味着,我们的函数不再绑定标识符“it”,因此不能再使用。是的,有点不妙。在下一节中,您将学习如何将值绑定到标识符,以允许您根据需要访问这些值。

               

下一步

练习 2:使用 F# 中的“Let”关键字

练习 2:使用 F# 中的“Let”关键字

严格来说,像 F# 之类的功能性语言没有变量。相反,值可以绑定到标识符,因此可以在程序的任何地方引用它们。相应地,F# “let”关键字使您能将任何表达式分配到标识符。

在本练习中,您将学习如何使用 let 关键字绑定值与标识符。

任务 1 –使用 Let 关键字将值绑定到标识符

1.            如果尚未打开,则打开 F# 交互控制台(关于指南,请查看练习 1 的开始部分)

2.            在 F# 交互命令提示符处输入以下命令:

F#

let a = 42;;

Response

val a :int = 42

3.            值 42 现在可以通过绑定到的标识符 a 进行访问。要验证这一点,在 F# 交互控制台提示符处输入以下命令:               

F#

a;;

Response

val it :int = 42

注意: 在类似 F# 的功能性语言中,值不是分配给变量的,而是绑定到标识符。这是因为,在纯功能性语言中,一旦将值分配给某个标识符,它将无法更改。如果开发人员将值绑定到标识符(即“let x = ‘42;;”),然后在相同的范围内,将新值绑定到相同名称的标识符(即“let x = ‘forty two’”)。F# 将创建一个新的标识符,并提供相同的名称。前一个标识符(在本例中,是绑定到 42 的那个标识符)仍然存在,但是已经不可达。

但是,F# 不是一种纯功能性语言。通过使用可变关键字和左 ASCII 箭头 ( <- ),开发人员可以创建一个可以更改值的标识符。可变关键字用于定义初始值,而左箭头用于更改值。更改值是可能的;但是,类型无法更改。

4.            标识符可以用于计算。在 F# 交互命令提示符处输入以下命令;

F#

a + 1;;

Response

val it :int = 43

F# 在计算中使用绑定标识符并返回结果。

5.            记住,除非使用 mutable 关键字,否则 F# 中的值将无法更改。在 F# 交互命令提示符处输入以下命令:

F#

let a = 0;;

a;;

Response

val it :int = 0

注意: F# 将值 0 绑定到名为 a 的标识符(如上所示,前一个 a 标识符仍然存在,但已经不可达)。

6.            因为将新值绑定到 a 将导致 F# 创建一个名为 a的新标识符,所以绑定的新值可以是完全不同于之前的绑定值的类型。在 F# 交互命令提示符处输入以下命令:

F#

let a = "42";;

Response

val a :string = "42"

注意:   您可以看到,即使前一个 a 包含整数值,新的 a 还是可以包含字符串。

               

任务 2 –处理元组和标识符

在本任务中,您将了解元组如何组成绑定到值的标识符,以及如何分解为可以绑定到标识符的单个值。

1.            对于本示例,我们需要在 F# 中定义的元组。在 F# 交互命令提示符处输入以下命令:

F#

let a = (42, "Hello F#");;

Response

val a :int * string = (42, "Hello F#")

注意: 这将创建类型为 int * string 的元组(该结构包含一个整数和一个字符串);

2.            F# provides the functions fst and snd to break apart tuples made up of two elements.在 F# 交互命令提示符处输入以下命令:

F#

let b = fst a;;

Response

val b :int = 42

F#

let c = snd a;;

Response

val c :string = "Hello F#"

F# 将第一个元组元素绑定到 b,将第二个元素(字符串)绑定到 c。将以下代码输入到交互控制台,以验证标识符的值:

F#

b;;

Response

val it :int = 42

F#

c;;

Response

val it :string = "Hello F#"

注意: 正如您所见,b 和 c 现在绑定到元组 a 的各个值。

3.            还有一种将单个元组值绑定到标识符的方法,即使用 let 语句将元组中的值绑定到标识符模式。在 F# 交互命令提示符处输入以下命令:

F#

let (b,c) = a;;

Response

val c :string = "Hello F#"

val b :int = 42

F#

b;;

Response

val it :int = 42

F#

c;;

Response

val it :string = "Hello F#"

注意: F# 可以使用模式匹配功能将元组中的每个值分别绑定到 b 和 c 标识符。

4.            如果元组只有两个值,使用 fst 和 snd 函数完全没有问题。对于具有两个以上值的元组,F# 允许使用 let 语句将元组元素绑定到模式。向 F# 交互控制台输入以下命令:

F#

let a = (42, "Hello F#", 42.0);;

Response

val a :int * string * float = (42, "Hello F#", 42.0)

由于该元组由多个元素组成,fst 和 snd 不再适用。实际上,如果您尝试使用这些方法,F# Interactive 将提示异常。

 

图 2

fst 和 snd 的失败

5.            尽管 fst 和 snd 仅适用于包含两个元素的元组,但模式匹配可以在任何情况下使用,无论元组中包含多少个值。在 F# 交互命令提示符处输入以下命令:

F#

let (b, c, d) = a;;

Response

val d :float = 42.0

val c :string = "Hello F#"

val b :int = 42

我们可以通过查看绑定到每个标识符的值来验证这一点。向 F# 交互控制台输入以下内容:

F#

b;;

c;;

d;;

6.            使用标识符模式从元组获取单个值时,有时您可能只需要一个可用值子集。F# 为模式匹配语法提供了一种机制,可以忽略不需要的值。只需要使用下划线字符 (_) 替换模式中的标识符,您就可以使 F# 在执行模式匹配时忽略该值。在下例中,我们只希望从元组中获取字符串值(第二个值),我们不关心第一个或第三个值。在 F# 交互命令提示符处输入以下命令:

F#

let (_, e, _) = a;;

Response

val e :string = "Hello F#"

注意: F# 只绑定我们在模式中提供的标识符,在本例中,将 e 绑定到字符串“Hello F#”。

               

下一步

练习 3:函数

               

练习 3:函数

F# 将函数视为另一种数据类型,因此在处理函数时我们拥有许多灵活性。在本练习中,您将继续使用F# 交互控制台,熟悉 F# 处理函数的方式。

任务 1 –绑定函数与标识符

在本任务中,您将了解如何将函数绑定到标识符来求值。

1.            创建一个函数并将其绑定到标识符 addTenToNumber。在 F# 交互命令提示符处输入以下命令;

F#

let addTenToNumber = (fun x -> x + 10);;

Response

val addTenToNumber :int -> int

这将函数绑定到标识符 addTenToNumber。该函数使用 fun 关键字声明,后跟参数(或参数列表)、-> 运算符,最后是函数正文。

注意: F# 将标识符 addTenToNumber 绑定到我们的类型,该类型定义为 int -> int。这表示绑定到 addTenToNumber 的值是一个函数,带有一个整数类型的参数,并返回一个整数。

2.            我们可以通过标识符引用该函数,以进行调用。在 F# 交互命令提示符处输入以下命令;

F#

addTenToNumber 32;;

Response

val it :int = 42

               

任务 2 –绑定函数与多个参数

在本任务中,您将了解 F# 如何处理带有多个参数的函数。

1.            首先,我们需要创建一个带有多个参数的函数。要构建我们最后的 addTenToNumber 示例,我们将创建一个更一般的函数,名为 addTwoNumbers。在 F# 交互命令提示符处输入以下命令:

F#

let addTwoNumbers = (fun x -> (fun y -> x + y));;

Response

val addTwoNumbers :int -> int -> int

注意: 这里用来声明带有两个参数的函数的语法进一步证明了,在 F# 中,函数就是值。仔细阅读该代码。第一件要注意的事是 addTwoNumbers 实际上不是一个函数,而是两个函数:一个函数接受第一个参数并返回,另一个函数接受第二个参数。

这与如今传统 .NET 语言中的函数行为完全不同。

您闪过的第一个念头可能是“喔!这种语法太糟了,太难理解了”。幸运的是,有更加容易的快捷语法,您将发现它很简练。但是在您理解了函数的底层定义方式之后,就很容易理解其他高级概念,比如“部分应用的函数”。

2.            我们可以调用函数将两个数相加,看看它是否能按我们预期的方式运行。在 F# 交互命令提示符处输入以下代码:

F#

addTwoNumbers 21 21;;

Response

val it :int = 42

3.            尽管前一个例子中使用的语法有效(并且它与 F# 处理函数的方式更加一致),但是仍然很难处理,尤其是在参数数量继续增加时。即使是在我们只有两个参数的场景中,代码也很难理解。幸运的是,F# 提供了一种声明函数的简单语法。在 F# 交互命令提示符处输入以下命令;

F#

let addTwoNumbers x y = x + y;;

Response

val addTwoNumbers :int -> int -> int

注意: F# 已经将我们的函数(它带有两个整数)绑定到标识符 addTwoNumbers。注意,F# 签名仍然是“int -> int -> int”,表示这是一个“带有 int 参数且返回一个函数的函数,返回的函数也带有 int 参数并返回 int”。没错,要多读几遍才能明白它的意思!

务必记住,在 F# 中,函数的参数通常不是像 C# 那样括在括号里面的。要声明一个带有两个参数的函数,语法为:

let functionName x y = …;;

这与以下语法是不同的:

let functionName (x, y) = …

在后者中,您告诉 F# 您的函数只需一个参数(一个元组)作为函数参数。

4.            与其他值一样,F# 的函数返回的值可以绑定到标识符。向 F# 交互控制台输入以下命令:

F#

let a = addTwoNumbers 21 21;;

Response

val a :int = 42

F#

a;;

Response

val it :int = 42

               

任务 3 –处理部分应用的函数

由于 F# 中定义函数的方式,F# 很自然地使用了“部分应用的”函数(也称为“扩充”函数)。

1.            部分应用的函数不需要应用所有输入参数就可以调用。从部分应用的函数返回的是一个函数,其参数是函数链中下一个期望参数。首先,我们需要重新创建一个带有两个整数值参数的函数。在 F# 交互命令提示符处输入以下代码:

F#

let addNumbers x y = x + y;;

Response

val addNumbers :int -> int -> int

2.            我们传递两个整数来调用该函数,并查看结果。在 F# 交互命令提示符处输入以下命令:

F#

addNumbers 20 22;;

Response

val it :int = 42

3.            为了展示部分应用的函数的能力,我们重写 addTenToNumber 函数,以部分地应用 addNumbers 函数。在 F# 交互命令提示符处输入以下命令:

F#

let addTenToNumber = addNumbers 10;;

Response

val addTenToNumber :(int -> int)

注意: 注意,尽管 addNumbers 函数需要两个参数,但我们仅使用一个参数调用它。记住 F# 中定义函数的方式。我们的 addNumbers 函数实际上定义为“let addNumbers = (fun x -> (fun y -> x + y))”。因此,当我们使用一个参数调用 addNumbers 时,将返回一个带有第二个参数的 function。按照这种思路,addNumbers function 是部分应用的。这将返回 function,然后绑定到 addTenToNumber。

4.            在本例中,标识符 addTenToNumber 绑定到一个带有整数参数并返回整数的函数。当传递第一个参数时,这正好是 addNumbers 函数的输出。通过在 F# 交互命令提示符处输入以下代码,我们可以调用绑定到 addTenToNumber 的函数:

F#

addTenToNumber 32;;

Response

val it :int = 42

下一步

练习 4:列表

               

练习 4:列表

本练习将演示 F# 如何处理列表。这包括,F# 对链接列表的实现与命令性编程语言中的巨大区别,解释 Head + Tail 方法并演示该方法通过使用递归函数和模式匹配给我们的代码带来的能力。

任务 1 –创建列表

在本任务中,您将学习在 F# 中创建列表的语法。

注意:F# 就像所有其他函数语言一样,将列表实现为链接列表。但是,F# 中链接列表的实现与命令性语言有一点不同。大部分命令性语言通过创建节点来实现链接列表,该节点包含一个值和一个指向列表中下一个节点的指针。这使得在列表中间添加项目变得很容易。F# 将列表实现为 Head 和 Tail。基本而言,F# 中的列表项节点由一个值和一个 tail 组成,后者本身也是一个列表。

有关 F# 处理列表的更多信息,请参考 Dustin Campbell 的博客:http://diditwith.net/2008/03/03/WhyILoveFListsTheBasics.aspx

1.            在 F# 中创建列表的一种方式是,指定用双冒号 (::) 分隔的列表,在列表最后使用空列表 ([])。严格来说,F# 将其看做串联列表:列表中的每个元素都可以视为串联到另一个元素或者一个空列表。最终结果是相同的;F# 将串联的值看作列表。以下示例代码创建了三个列表;一个空列表,一个带有一个项的列表以及一个带有两个项的列表。向 F# 交互控制台输入以下代码:

F#

let emptyList = [];;

Response

val emptyList :‘‘a list

F#

let listWithOneItem = "one" :: [];;

Response

val listWithOneItem :string list = ["one"]

F#

let listWithTwoItems =  "one" ::"two" :: [];;

Response

val listWithTwoItems :string list = ["one"; "two"]

注意: 注意,在所有情况下,F# 始终可以正确地自动推理出列表的类型。尤其有趣的是我们创建的第一个列表。‘‘a 是什么意思?这通常表示这是一个泛型列表,将在使用时推理其类型。

还要注意,整个实验到目前为止,我们还没有指定过一次类型。因此,许多开发人员误认为 F# 是一种动态语言。但它不是。它实际上是一个静态类型语言。但是,F# 对类型推理的广泛使用减少了许多其他 .NET 语言所需的指定类型的过程。

2.            F# 不允许列表包含不同的类型。例如,以下示例代码将无法编译,因为它试图将字符串和整数放入同一个列表:

F#

let badList = "one" :: 2 :: 3.0 :: [];;

注意: 由于这些值的类型不同,F# 无法将它们放入同一个列表。

 

图 3

糟糕的列表

3.       上述语法在串联列表中非常有用,但是对于从头创建的列表则不实用。F# 为创建列表提供了一种快捷方法。在中括号之间放入分号分隔值将在 F# 中定义列表。向 F# 交互控制台输入以下代码:

F#

let easyList = ["A"; "B"; "C"];;

Response

val easyList :string list = ["A"; "B"; "C"]

4.            由于 F# 实现列表的方式,很容易将两个列表组合为一个列表。以下示例将创建两个列表,然后将其组合为第三个列表。向 F# 交互控制台输入以下代码:

F#

let firstList = ["A"; "B"; "C"];;

Response

val firstList :string list = ["A"; "B"; "C"]

F#

let secondList = ["1"; "2"; "3"];;

Response

val secondList :string list = ["1"; "2"; "3"]

F# 将使用合适的值创建两个列表。现在,为了组合这些列表,F# 使用 @ 运算符。向 F# 交互控制台输入以下代码:

F#

let combinedList = firstList @ secondList;;

Response

val combinedList :string list = ["A"; "B"; "C"; "1"; "2"; "3"]

F# 创建了新列表。为了验证新列表是我们的组合列表的值,我们可以将以下代码输入交互控制台查看其内容:

F#

combinedList;;

Response

val it :string list = ["A"; "B"; "C"; "1"; "2"; "3"]

               

下一步

练习 5:模式匹配和递归

练习 5:模式匹配和递归

F# 中的模式匹配与 C# 中的 switch 语句类似;但是它们提供的能力和功能比 C# 结构要强大,尤其是与 F# 的列表实现和递归同时使用时。在本练习中,您将了解 F# 的模式匹配语法,并查看利用模式匹配能力的一些示例。

任务 1 –使用简单的模式匹配输出信息

在任务中,您将学习 F# 中基本的模式匹配语法。

1.            F# 中的模式匹配语法非常直观。在 F# 交互控制台命令提示符处输入以下代码:

注意:空格在 F# 控制台中非常重要。您至少需要一个空格字符才能在第一行之后进行缩进。惯例是使用 4 个空格,而不是一个制表符。

F#

let patMatch x =

    match x with

    | 0 -> printfn "Value is 0"

    | 1 -> printfn "Value is 1"

    | _ -> printfn "Value is not 0 or 1";;

Response

val patMatch :int -> unit

2.            通过使用 | 运算符堆叠模式,可以比较输入值和各种模式。在 F# 交互命令提示符处输入以下代码:

F#

let patMatch x =

    match x with

    | 0 | 1 -> printfn "Value is 0 or 1"

    | _ -> printfn "Value is not 0 or 1";;

Response

val patMatch :int -> unit

3.            F# 还可以提供语法来在元组上轻松执行模式匹配。下一个示例将演示如何使用 if 控制结构来“anding”两个可以使用模式匹配实现的值。在 F# 交互命令提示符处输入以下代码:

F#

let andTrue x =

    match x with

    | (true, true) -> true

    | _ -> false;;

Response

val andTrue :bool * bool -> bool

该函数使用由两个 boolean 值组成的元组,并返回一个 boolean 值。

4.            我们可以使用以下元组调用来验证该代码:

F#

andTrue (false, true);;

Response

val it :bool = false

F#

andTrue (true, true);;

Response

val it :bool = true

               

任务 2 –使用简单的模式匹配和递归实现斐波那契数列 (Fibonacci Sequence)

在本任务中,您将学习在 F# 中使用递归函数补充模式匹配。递归也就是使用函数本身来定义函数。在以下示例中,如果 x 不是 0 或 1,则函数调用本身,传递从 x 中继承的值。功能性编程常常使用该方法替代循环,因为它可以让算法更容易理解。

1.            要在 F# 中创建递归函数,您必须使用 rec 关键字。以下示例是一个斐波那契函数的 F# 实现,使用递归来调用自身,以确定结果。在 F# 交互控制台命令提示符处输入以下代码:

F#

let rec fib x =

    match x with

    | 0 -> 0

    | 1 -> 1

    | _ -> fib(x - 1) + fib(x - 2);;

Response

val fib :int -> int

注意: 斐波纳契数列是一个数字序列,序列中的后一个数是前两个数之和(最开始两个数是 0 和 1)。例如,以下是斐波纳契数列的前 10 个数。

0, 1, 1, 2, 3, 5, 8, 13, 21, 34

2.            使用值 42 调用函数:

F#

fib 42;;

Response

val it :int = 267914296

注意:F# 中使用 rec 关键字表示函数是递归的。

               

任务 3 –在 F# 列表中使用递归

F# 的递归能力尤其适用于处理列表中的项。该任务将模拟以递归方式对列表中的项执行某种处理。

1.            下面的函数将列表作为一个参数。这里我们将利用模式匹配的能力。注意,模式匹配要使用 ::运算符,我们之前构建列表时也使用了该运算符。因此,如果传入的列表为空列表,我们就打印“done”。否则,我们将继续打印 head 的值,然后再次递归调用函数并处理 tail。在 F# 交互控制台命令提示符处输入以下代码:

F#

let rec processList x =

    match x with

    | head::tail -> printfn "processing %i" head; processList tail

    | [] -> printfn "done";;

Response

val processList :int list -> unit

注意:在 F# 中,可以使用分号分隔同一行中不同的语句。

还要注意,只要我们打印的字符串使用格式“%i”(用于 int),F# 就能够推理出函数接受整数列表。

2.            我们将创建一个 list,将其发送到函数以供处理。在 F# 交互控制台命令提示符处输入以下代码:

F#

processList [1; 2; 3; 4; 5];;

Response

processing 1

processing 2

processing 3

processing 4

processing 5

done

val it :unit = ()

               

下一步

练习 6:类型和可辨识联合

               

练习 6:类型和可辨识联合

像所有的现代语言一样,F# 提供了创建用户自定义类型的实用工具。F# 还允许开发人员创建“可辨识联合”来表示数据或结构,并使用的方式传递不同的含义。

以下练习将演示如何在 F# 中创建简单的类型,如何创建可辨识联合,然后在模式匹配算法中使用这些联合。

任务 1 –在 F# 中创建一个简单的类型

在本任务中,您将在 F# 中创建一个简单的类型。

1.            我们将创建一个类型来保存商品名称和单价。我们首先将商品名称和单位成本定义为类型别名。在 F# 交互控制台中,输入以下命令:

F#

type ItemName = string;;

Response

type ItemName = string

F#

type UnitPrice = float;;

Response

type UnitPrice = float

2.            F# 中的记录类型类似于类或结构,它们都是由不同值组成的类型。我们可以在记录类型中使用标准的原 F# 类型,或者用户定义的类型。在 F# 交互控制台中,输入以下命令:

F#

type OrderItem = {ItemOrdered :ItemName; Quantity :int; PricePer :UnitPrice};;

Response

type OrderItem =

    {ItemOrdered:ItemName;

     Quantity:int;

     PricePer:UnitPrice;}

注意: 创建记录类型的语法非常简单。字段包含在大括号中,每个字段都使用分号分隔。字段本身由一个名称和一个使用冒号分隔的类型。您可以看到,ItemOrdered 和 PricePer 字段由我们定义的类型别名组成,但是我们还可以将 Quantity 字段定义为 F# 类型。

               

               

任务 2 –创建可辨识联合

在本任务中,您将了解如何使用可辨识联合保存温度信息。尽管 Celsius、Fahrenheit 和 Kelvin 温度非常不同,但可辨识联合能够表示正确的值。可辨识联合由两部分信息组成:值类型(在 F# 中称为构造函数)和值本身。

1.            创建一个可辨识联合类型来保存我们的温度数据。在 F# 交互控制台中,输入以下命令:

F#

type temperature =

| Celsius of float

| Fahrenheit of float

| Kelvin of float;;

Response

type temperature =

| Celsius of float

| Fahrenheit of float

| Kelvin of float;;

在本例中,构造函数是 Celsius、Fahrenheit 和 Kelvin,都接受一个浮点类型的值。

2.            要使用类型,我们只需声明一个标识符,并告知 F# 我们将提供哪种值类型。在 F# 交互控制台中,输入以下命令:

F#

let temp1 = Celsius 32.0;;

Response

val temp1 :temperature = Celsius 32.0

F#

let temp2 = Fahrenheit 98.6;;

Response

val temp2 :temperature = Fahrenheit 98.6

F#

let temp3 = Kelvin 5.0;;

Response

val temp3 :temperature = Kelvin 5.0

尽管这些值的类型各不相同,但它们都表示温度。

3.            从这些值中我们可以看到,它们除了存储数据之外,还存储了数据类型。向 F# 交互控制台输入以下内容:

F#

temp1;;

Response

val it :temperature = Celsius 32.0

               

               

任务 3 –在可辨识联合中使用模式匹配

在本任务中,您将了解如何使用模式匹配来解构可辨识联合。

1.            以我们的温度示例为基础,我们将创建一些函数来转换温度。将以下命令输入 F# 交互控制台来创建这些函数:

F#

let convertToFahrenheit x =

    match x with

    | Celsius x -> Fahrenheit (x * (9.0 / 5.0) + 32.0)

    | Fahrenheit x -> Fahrenheit x

    | Kelvin x -> Fahrenheit (x * (9.0 / 5.0) - 459.67);;

Response

val convertToFahrenheit :temperature -> temperature

2.            通过调用这些函数并传入我们之前创建的温度值,我们可以看到,它们能够基于每个联合的构造函数执行模式匹配,以确定要应用哪个算法。

F#

convertToFahrenheit temp1;;

Response

val it :temperature = Fahrenheit 89.6

               

下一步

总结

               

总结

在本实验中,我们介绍了微软公司新的功能性编程语言 F#。F# 将函数当作一类居民,它支持元组、模式匹配,它的简洁性让它成为了非常强大的多范式语言。