处理函数最重要的操作,就是组合。先看一个示例是非常有用的,这个示例用元组保存(城市的)名字和人口。在清单 6.16 中,我们创建一个函数,根据人口的规模,确定是城市、镇,还是村;同时用保存在列表中的几个地方测试确定状态。
清单 6.16 处理城市信息 (F# Interactive)
> let places = [("Grantchester", 552); <-创建测试数据列表
("Cambridge", 117900);
("Prague", 1188126); ];;
val places : (string * int) list
> let statusByPopulation(population) = <- 根据人口返回状态
matchpopulation with
|n when n > 1000000 -> "City"
|n when n > 5000 -> "Town"
|_ -> "Village";;
val statusByPopulation : int –> string
> places |> List.map (fun (_,population) –> [1] <- 迭代地方,读人口信息
statusByPopulation(population));; [2] <- 计算状态
val it : string list =["Village"; "Town"; "City"]
清单 6.16 的第一部分(创建了测试数据的列表,声明 statusByPopulation 函数)非常简单,重要的在最后几行。我们想使用 List.map 获取每个地方的状态,因此,把 lambda 函数作为参数值传递它。lambda 函数首先使用模式匹配[1],从元组中提取第二个元素,然后,调用 statusByPopulation 函数[2]。
代码虽然能够运行,但还可以写得更优雅。关键的思想是,必须依次执行两个运算首先要访问元组中的第二个元素,然后,用返回的值进行计算。第一个运算可以使用 snd 函数实现,因此,需要组合两个函数。在 F# 中,这可以使用函数组合运算符(>>)来写,像这样:
snd >> statusByPopulation
这个运算的结果是函数,参数为元组,读元组的第二个元素(必须是整数),根据这个数计算出城市的状态。看一下表 6.1,我们可以了解函数是如何组合的,表显示了函数的类型签名。
表 6.1 类型签名:snd,statusByPopulation,和通过使用 >> 运算符组合两个函数获得的函数
函数值 |
类型 |
snd snd (after specification) statusByPopulation snd >> statusByPopulation |
(‘a * ‘b) -> ‘b (‘a * int) -> int int -> string (‘a * int) –> string |
表格的第二行表明了 snd 函数的特定类型,编译器推断出元组的第二个元素必须是整数;如果我们把第一行中的类型参数 ‘ b 用 int 类型替换,就得到这个类型。现在,我们有了两个可以组合的函数,因为,第二行函数的返回类型与第三行函数的输入类型相同;通过组合,可以把这两个函数连接到一起,得到的函数,调用第一个函数,并把这个调用的结果传递给第二个函数,最终函数的输入类型与第二行函数相同,返回类型与第三行函数相同。清单 6.17 显示了用函数组合重写原来的代码。
清单 6.17 使用函数组合运算符 (F# Interactive)
> places |> List.map (fun x ->(snd >> statusByPopulation) x);; [1]
val it : string list =["Village"; "Town"; "City"]
> places |> List.map (snd >>statusByPopulation);; [2]
val it : string list =["Village"; "Town"; "City"]
第一行[1]显式调用组合函数,把包含城市名和人口的元组作为参数值。这演示了组成的结果是函数,可以使用普通的语法来调用;然而,使用函数组合的原因是能够把组合函数作为给其他函数的参数值来使用。这里,组合函数的参数为元组,返回字符串,因此,我们可以直接把它作为 List.map 的参数值来使用,得到样本地点状态的列表[2]。
函数组合运算符的实现非常简单;如果F# 库中不存在这个运算符,我们自己也可以定义它:
> let (>>) f g x = g(f(x))
val (>>) : (‘a -> ‘b) -> (‘b-> ‘c) -> ‘a -> ‘c
在这个声明中,运算符有三个参数。我们早先使用时,只指定前两个参数(函数被组合要。更深入了解组合函数的工作原理,可以看一下图 6.2 中对类型签名的两种可能解释。
图 6-2 函数组合运算符的类型签名。如果指定三个参数值(上面的注释),依次返回调用的结果;如果只指定两个参数值(下面的注释),返回组合函数。
这个运算符之所以能够组合函数,是因为有了散应用。如果只指定前两个参数值,结果是组合函数,当运算符接收第三个参数值时,就使用这个参数值调用第一个函数,然后,使用这个结果去调用第二个函数。很明显,如果指定所有三个参数值,通常没有多大用处,因为我们能够直接调用函数,不必使用这个运算符!
我们已经了解 F# 中函数组合的工作原理,现在,就让我们看一下在 C# 中会是怎样。