6.2.1 使用函数处理元组

6.2.1 使用函数处理元组

 

    在第 3 章,我们用元组来表示城市和人口。当我们想要增加人口时,不得不写点东西,像这样:

 

let (name, population) = oldPrague
let newPrague = (name, population + 13195)

 

    这很清晰,但有点罗唆。第一行分解元组,第二行对第二个元素执行计算,然后,生成新的元组。理想情况下,我们可能想说,我们要对第二个元素执行分解计算,然后重建这个元组。首先,让我们看一下我们希望能够写的代码, F# 和 C#,然后,将实现方法,使其工作。这就是我们的目标:

 

let newPrague = oldPrague |> mapSecond ((+) 13195)       //F#
var newPrague = oldPrague.MapSecond(n => n + 13195);  //C#

 

    此版本删除了所有额外的代码,以重建该元组,并指定核心理念,即,我们要把元组中第二个元素中加上某个数。要在第二个元素上执行计算的想法,是通过使用 F# 中的 mapSecond 函数表示的。清单 6.4 显示了这个函数和类似的 mapFirst 的实现。

 

Listing 6.4 Higher-order functions for working with tuples (F# Interactive)

 

> let mapFirst f (a, b) = (f(a), b)
   let mapSecond f (a, b) = (a, f(b))
;;
val mapFirst : ('a -> 'b) -> 'a * 'c -> 'b * 'c
val mapSecond : ('a -> 'b) -> 'c * 'a -> 'c * 'b

 

    清单 6.4 实现两个函数:一个是在元组的第一个元素上执行操作,另一个是作用于第二个元素。这两个函数的实现很简单:我们在参数列表中使用模式匹配,以分解给定的元组,然后,以元素之一调用该函数。最后,我们返回一个新的元组,由函数调用的结果与其他元素的原始值组成。虽然,函数体看起来不难,推导出的类型签名看起来却相当复杂,当你第一次看到它们时。很快我们会再回来。

 

映射操作

 

    我使用术语映射(map),我们刚才讨论的函数的名称。映射(也称为投影)是一项常见的操作,并正如你将要看到的,它可以与许多数据类型一起使用。一般情况下,它取一个函数作为参数值,并应用此函数一个,或有时多个值,存储在这个数据类型中。结果被包在具有相同结构的数据类型中,并返回作为这个映射操作的结果。结构并未改变,因为,我们所指定的操作不会告诉我们用这个组合值来做什么。它只指定用这个值的组件做什么,不知道其他,投影必须保留原始结构。现在,这个描述可能不完全清楚,因为,这很大程度上取决于你获得的直觉,在本章后面更类似的操作之后。

 

    这些函数的签名,对于了解它们在做什么是有用的。图 6.1 分解了 mapFirst 的签名,并显示每个部分的意思。

image

 

图 6.1 mapFirst 函数取一个函数作为第一个参数值,并将其应用到元组的第一个元素,元组作为第二个参数值传递。

 

    让我们看看这个签名能告诉我们有关这个函数的什么内容。首先,它是一个泛型函数,有三个类型参数,由 F# 编译器自动命名。它取一个函数作为第一个参数,元组包含类型为 'a 和 ' c 的值,作为第二参数值。这个签名告诉我们,返回的元组由类型为 'b 和 'c 的值组成。

    因为这个函数不具有任何安全的方式,来处理 ' c 类型的值,它可能是只复制第二个元素。接下来的问题,是我们怎样才能在结果中,获得 ' b 类型的值。我们有一个 ' a 类型的值(元组的第一个元素),和一个函数,它可以将一个 'a 类型的值转换成 ' b 类型的值,所以,最明显的解释是,mapFirst 适用这个函数到元组的第一个元素。

    现在,我们已经实现了 mapFirst 和 mapSecond 函数,让我们开始使用它们。清单 6.5 显示了F# Interactive 会话演示如何能用来处理元组。

 

Listing 6.5 Working with tuples (F# Interactive)

 

> let oldPrague = ("Prague", 1188000);;
val prague : string * int

> mapSecond (fun n -> n + 13195) oldPrague;;
val it : string * int = ("Prague", 1201195)

> oldPrague |> mapSecond ((+) 13195);;
val it : string * int = ("Prague", 1201195)

 

    该示例演示了两种方来法写相同的操作,使用 mapSecond 函数。第一种情况,我们直接调用这个函数,给它 lambda 函数作为第一个参数值,原始的元组作为第二个参数。如果你看看由 F# Interactive 打印的结果元组,可以看到这个函数应用到元组的第二个元素,正是我们想要的。

    在第二个版本中,我们使用两个强大的技术。我们使用了偏函数应用(在前一章中介绍的)来创建一个函数,为第二个元素加上  13195。不用显式写 lambda 函数,我们写了 (+) 13195。如果在括号中使用运算符,它的行为像普通函数,这意味着,我们可以通过写 (+) 10 5 来加两个数。如果我们使用偏函数应用,并只给它有一个参数值,我们就获得了一个 int �C> int 类型的函数,加这个数到任意给定的参数值,类型与 mapSecond 函数预期的兼容。类型是 'a -> 'b,在这种情况下, int 替换为 'a 和' b。

    由于有了流,我们可以写原始的元组,然后是应用的这个函数。这使代码更具可读性,首先,描述我们要去操作什么;然后,我们打算用它做什么――就像在 C# 中,操作是通常的目标形式,MethodToCall()。使用流也是原因之一,mapSecond 为什么需要取一个函数作为第一个参数值,并且,元组作为第二个参数值,而不是相反。

    我这一节开始讨论了 F#,因为,显示推导出高阶函数的类型签名,使用流可以非常自然地在 F# 中演示。当然,我们可以在 C# 中,使用相同的概念,我们将在下一节这样做。

你可能感兴趣的:(职场,元素,休闲,函数处理元组)