12.1.3.2 组合序列表达式
在 C# 中,yield return 关键字只能返回一个元素,因此,如果我们要在 C# 中,使用迭代器实现的方法中,产生整个序列,那么,就必须使用 foreach 循环,遍历序列中所有元素,逐个产生元素。这也能运行,但效率低下,特别是,如果用这种方式处理几个嵌套的序列。在函数式编程中,可组合性(composability)是一个更重要的概念,所以,F# 能够组合序列,使用专门的语言表达结构:yield!(通常发音 yield-bang),从序列表达式生成整个序列。清单 12.3 演示了以三种不同的方法生成城市序列。
清单 12.3 从不同的数据源组合序列 (F# Interactive)
> let capitals = [ "Paris";"Prague" ];; <-- 首都列表
val capitals : string list
> let withNew(name) = <-- 返回名字和名字加前缀
seq { yield name
yield "New " + name };;
val withNew : string ->seq<string>
> let allCities =
seq { yield "Oslo" [1]
yield! capitals [2]
yield! withNew("York") };; [3]
val allCities : seq<string>
> allCities |> List.ofSeq;;
val it : string list = ["Oslo";"Paris"; "Prague"; "York"; "New York"] <-- 把所有数据组合在一起
清单 12.3 首先创建两种不同的数据源。第一个是 F# 列表,有两个首都,值的类型是 list<string>;因为 F# 列表实现了 seq <'a> 接口,因此,在后面的代码中能够将它当作序列来使用。第二个数据源是函数,生成有两个元素的序列。接下来的代码段演示了将如何这两个数据源联接成一个序列。首先,使用 yield 语句返回一个值[1];接着,使用 yield! 结构返回 F# 列表中的所有元素[2];最后,调用 withNew 函数[3](返回一个序列),返回序列中的所有元素。从这里可以看出,可以在一个序列表达式中,混合使用两种产生元素的方法。
就像 yield 一样,yield! 结构也是延迟返回元素。就是说,当代码到达调用 withNew 函数时,这个函数才真正调用,而且,只返回表示该序列的对象。如果我们在函数中写的代码,放在 seq 块之前,它会在这一点上执行,但是,seq 块的主体并不会执行。它要到 withNew 函数返回之后才发生,因为我们需要生成下一个元素。当执行到达第一个 yield 结构时,它会返回元素,并将控制权返回到调用者。调用者然后执行其他操作,当调用者请求另一个元素时,序列的执行才恢复。
我们一直专注于序列表达式的语法,但是,只有开始使用,才不会觉得棘手。使用序列表达式时,有几个常用的模式,我们看其中的两个。