原则上,我们可以使用查询处理任何类型,只要它提供了绑定操作。这是函数式编程中这类函数的标准名称,像上一节类型签名所展示的。从技术角度来讲,我们需要实现一些方法,在把查询表达式转换为标准的函数调用,由 C# 编译器所使用。我们将为 12.6 节中的 Option<T> 的类型实现这些方法,该类型没有实现 IEnumerable<T>,所以,不能使用标准查询运算符。
我们首先考虑一下,查询应用到选项类型,是什么意思。清单 12.15 有两个查询,左边的处理列表,右边的处理选项类型。我们将使用两个简单的函数来提供输入:ReadIntList 函数读取整数列表(类型为 List<int>),TryReadInt 返回选项值(类型为 Option<int>)。
清单 12.15 对列表和选项值使用查询 (C#)
var list = from n in ReadIntList() from m in ReadIntList() select n * m; |
var option = from n in TryReadInt() from m in TryReadInt() select n * m; |
除了处理的数据类型不同之外,查询完全相同,因此,它们使用不同的查询运算符实现。两者都读不同的输入,并返回输入整数的积。表 12.1 给出了样本输入和结果。
表 12.1 对于可能的不同输入,由使用列表和选项值的查询所产生的结果
Type of values |
Input #1 |
Input #2 |
Output |
Lists Options Options Options |
[2; 3] Some(2) Some(3) None |
[10; 100] Some(10) None Not required |
[20; 200; 30; 300] Some(20) None None |
对于列表,查询执行交叉联接运算(可以想象成 F# 序列表达式中的两个嵌套for 循环),会产生一个序列,由输入值的每种组合条目所组成;对于选项值,有三种可能性。
■当第一个输入是值时,需要读第二个。然后,根据第二个输入的不同,有以下两种情况:
— 如果第二个输入也是值,则结果是 Some 值,包含的结果是积。
— 如果第二个输入是 None,由于没有值相乘,因此,查询返回 None。
■当第一个输入为 None 时,我们知道,结果无需考虑第二输入。整个查询延迟执行,所以,不必去读第二输入:TryReadInt 函数只调用一次。
可以发现,查询表达式提供了一种简便的方法,处理选项值。清单 12.15 无疑要比我们在第六章看到的等价代码,容易写(和读),那时,我们显式地使用了高阶函数。在本章的后面我们将会看到,实现所有必要的查询运算符;不过,现在,我们还是先看一下在 F# 中的类似语法。