5.3.4 F# 中的选项(option)类型
我们常常需要表达这样的思想,某些计算可能会返回未定义的值;在 C# 中,通常用返回 null(空值)实现。不幸的是,使用 null 频繁导致错误:写代码,可能会轻易地假定方法不返回空,一旦这个假设不成立,就会看到NullReference 错误。当然,好代码总是在适当的地方检查值是否为空,为应用程序写的单元测试,大量的检查就是验证这种特定情况下的行为。
F# 使空值的使用最小化,通常只在与 .NET 类型进行互操作的情况下才使用。为了表示可能返回未定义结果的计算,我们使用选项(option)类型。当函数以 option 作为返回类型时,显式声明,结果可能是未定义的;同时,编译器强制调用程序处理未定义的结果。
选项类型是有两个可选值的差别联合,识别器 Some 用于创建有值的选项;None 用于表示未定义的值。清单 5.7 中的函数,从控制台读取输入,当用户没有输入数字时,返回未定义的值。
清单 5.7 读输入作为可选值 (F# Interactive)
> open System;;
> let readInput() =
lets = Console.ReadLine()
matchInt32.TryParse(s) with <-- 尝试解析输入
|true, parsed -> Some(parsed)
|_ -> None
;;
val readInput : unit -> int option
代码相当简单:读取输入,使用 TryParse 方法解析,用选项类型中的一种情况构造返回值。我们使用模式匹配中一个有趣和强大的功能来实现这个函数;match 构造的输入是从 TryParse 方法返回的元组。当解析成功,元组的第一个值为真,第二个值就是我们感兴趣的数值。要处理模式匹配内部的这种情况,我们指定第一个模式为常量 true,第二个模式为新建的值 parsed。当元组中第一个元素为真时,模式匹配把解析后的数字指定给值 parsed,使用 Some 返回这个结果。
第二个分支使用下划线模式来处理所有剩余的情况。在这种情况下,我们知道解析已经失败,所以,使用 None 返回未定义的结果。我们还可以 F# Interactive 看到输出的方法签名,返回 int option,就是说,option 类型是泛型,在这里,具有整型值。我们会在第 5.4.2 节看到如何定义这样的泛型类型。
现在,我们先看一下使用这个函数的代码。这里,我们看到使用选项类型的真正好处,语言会强制我们写处理未定义值的代码,因为访问值的唯一方法是使用模式匹配。可以在清单 5.8 中看到这个示例。
清单 5.8 处理使用选项类型的输入 (F# Interactive)
> let testInput() =
letinput = readInput() [1]
matchinput with [2]
|Some(number) –> <-- 包含正确的输入
printfn"You entered: %d" number
|None –> <-- 包含未定义的输入
printfn"Incorrect input!";;
val testInput : unit –> unit
> testInput();; | 检查第一种情况
42 |
You entered: 42 |
> testInput();; | 检查第二种情况
fortytwo |
Incorrect input! |
可以发现,我们不能在调用 readInput 函数[1]后直接使用这个值,这是关键的区别,它使程序更安全,因为当函数返回空值时,不必检查这种可能性。要在 F# 中读取这个值,必须使用模式匹配[2],我们为每个选项类型情况写了一个分支。我们已经知道,F# 会验证模式匹配是否完整,即,是否涵盖了所有可能的选项,这就保证了我们写的代码不会意外地只包含 Some 识别器分支。清单 5.8 也遵循 F# 的最佳做法,通过在 F # Interactive 中立刻测试代码,检查它的行为在两种情况下都正确。
可空(Nullable)和选项类型
F# 的选项类型在某些方面类似于 C# 中的Nullable<T> 类型,但是,更通用、更安全。在 C# 中,当我们想要表示一个缺失值(missing value),通常使用 Null,但这只能用于引用类型。可空类型能用于创建值类型,null 也是有效值。
在 F# 中,null,不是任何在 F# 中声明的类型的有效值(尽管,对于已有的 .NET 引用类型,它仍然是有效值)。因此,一理我们需要创建可能为空的任意值时,就要把实际类型包装到选项类型中。由于有了模式匹配,编译器也能够确保我们实现的代码,始终可以处理缺失值的情况。
现在,我们已经知道如何使用选项类型,以及对于 F# 编程的重要性,下面,就要讨论如何实现了。