12.3.2.2 使用查询和序列表达式
在 C# 3.0 中,我们可以使用新的查询表达式语法,写有关映射和筛选数据的操作。查询表达式还支持许多其他操作,但我们会只关注映射和筛选,来演示函数技术和 F# 功能。
F# 虽然没有专门提供的查询表达式,但是,使用序列表达式,仍可以轻松地写出映射和筛选数据的查询。这是因为序列表达式在 F# 中所有地方都可以使用,而不仅仅是返回序列的函数。清单 12.9 显示了使用 C# 中的查询和 F# 中的序列表达式,实现我们前面的示例。
清单 12.9 在 C# 和 F# 中的筛选和映射序列
C# |
F# |
var nums1 = from n in nums where n%3 == 0 select n * n; |
let nums1 = seq { for n in nums do if (n%3 = 0) then yield n * n } |
在 C# 中,查询表达式和迭代器是完全不同的,而在 F# 中,序列表达式表明,它们在概念上是相关的。查询表达式中的每个部分在 F# 中都有对应的结构,而且总是更加一般:from 子句由简单的 for 循环取代,where 子句由 if 条件取代,select 子句与有映射的 yield 语句相对应,表示为通常的计算。
C# 查询表达式语法还有另外一些运算符,用 F#序列表达式不容易表达。因此,C# 版本功能更强大,而 F# 的实现更加统一。
要注意一下C# 查询表达式和 F# 序列表达式内部的运行。C# 查询表达式使用 lambda 表达式,以明确的定义转化成一系列调用,比如:Where、Select、SelectMany、Join 和 GroupBy。这些通常是扩展方法,但也不一定,编译器不关心查询表达式的意思,只要翻译的代码是有效的就行。这个“数据源不可知论(datasource agnosticism)”对于数据处理的方法,比如LINQ to Objects 和 LINQ to SQL,至关重要,我们不久将使用它,展示查询语法如何用来处理其它种类的值。
另一方面,序列表达式能够表达更加复杂和多方面的结构。我们可以复制 yield 结构,把数据源中的一个项目返回为两个元素。这个使用 C# 中的迭代器可以很容易实现,但是,使用内嵌的查询语法,表示这种转换,是不可能的。
LINQ 中的其他查询运算符
在 C# 3.0 中,查询表达式语法是专为从各种数据源查询和格式化数据的,因此,它的操作不止映射和筛选。这些运算符主要就为此目的,在 F# 中没有专门对应的语法。所有这些标准运算符都可以用操作序列的常规高阶函数来实现。例如,排序数据:
var q = from c in customers orderby c.Name select c; [ 合理的话,这里也应该是 C.City ] |
let q = customers |> Seq.sortBy (fun c -> c.City) |
作为给 Seq.sortBy 运算符的第一个参数的函数,指定在比较两个元素时,应该处理元素的哪个属性。在 C# 的查询语法中,对应于跟在 orderby 子句后的表达式;C# 编译器使用 lambda 函数,将此表达式转换为对标准OrderBy 方法的调用。F# 中唯一查用的操作,是作为高阶函数的分组:
var q = from c in customers group c by c.City; |
let q = customers |> Seq.groupBy (fun c -> c.City) |
对序列进行分组,需要指定返回键的函数,键表示元素所属的组。C# 还是有专门的语法,而在 F# 的代码段中,我们使用标准的 lambda 函数。
在这些示例中,两个版本的代码看起来是合理的。然而,当我们写 F# 的代码,需要操作把只能使用高阶函数的映射和筛选混到一起,那么,等效的 C# 查询表达式会更容易理解。
在 F# 编译器中,序列表达式的实现进行了优化,但是,如果没有这些优化,它可能与 C# 查询表达式一样运行。任可序列表达式,都可以转化为标准的函数调用序列。类似于 C#,我们可以提供这些函数的自定义实现,这样,就可以更深入理解 F# 中转换是如何运行的。F# 语言使用数量很少的操作,严重依赖于一个操作,称为平面映射(flattening projection)。
原文地址:http://blog.csdn.net/hadstj/article/details/43062071