6.2.1 使用函数处理元组

728 篇文章 1 订阅
28 篇文章 0 订阅

6.2.1 使用函数处理元组

 

在第三章,我们用元组来表示城市和人口。当我们想增加人口时,写的代码像这样:

 

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 函数的实现。

 

清单 6.4 处理元组的高阶函数 (F# Interactive)

> let mapFirst f (a, b) = (f(a), b)    [1]

let mapSecond f (a, b) = (a, f(b))   [2]

;;

val mapFirst : ('a -> 'b) -> 'a * 'c-> 'b * 'c    | [3]

val mapSecond : ('a -> 'b) -> 'c * 'a-> 'c * 'b  |

 

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

 

映射操作

 

在刚才讨论的函数名中,我们使用了术语映射(map)。映射(也称为投影)是一个常见操作,我们将会看到,它可以用于许多数据类型。一般情况下,它取函数作为参数值,把这个函数应用到一个、有时可能是多个值上,这些值是保存在数据类型中的;结果被包在具有相同结构的数据类型中,并且,也作为映射操作的返回结果。结构没有改变,是因为我们所描述的操作不是针对组合值的,只而是描述了[组合]值的各个组件应该做什么,不需要知道其他内容,因此,映射必然保持原来的结构。现在,这个描述可能不完全清楚,因为,这很大程度上取决于你获得的直观感受,在本章后面有更类似的操作。

 

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

图 6.1 mapFirst 函数的第一个参数为函数,第二个参数为元组,把这个函数应用到元组中的第一个元素。

 

我们来看看签名能提供函数的哪些信息。首先,函数是泛型的,有三个类型参数,由 F# 编译器自动命名;它取函数作为第一个参数,包含类型为 'a 和 ' c值的元组,作为第二参数值。从签名可知,返回元组由类型为 'b 和 'c 的值组成。

因为这个函数没有任何安全的方式,来处理 ' c 类型的值,直接就复制了第二个元素。接下来的问题是,怎样才能在结果中得到 ' b 类型的值。已有 ' a 类型的值(元组中的第一个元素),和能够把 'a 类型值转换成 ' b 类型值的函数,因此,最明显的答案是,mapFirst 把函数应用到元组的第一个元素。

我们已经实现了 mapFirst 和 mapSecond 函数,现在,就可以使用了。清单 6.5 显示的F# Interactive 会话演示了如何用来处理元组。

 

清单 6.5 处理元组 (F# Interactive)

> let oldPrague = ("Prague",1188000);;

val prague : string * int

 

> mapSecond (fun n -> n + 13195)oldPrague;;  [1]

val it : string * int =("Prague", 1201195)

 

> oldPrague |> mapSecond ((+)13195);;    [2]

val it : string * int =("Prague", 1201195)

 

示例演示了两种方法,使用 mapSecond 函数,写的是同样的操作。第一种方法是直接调用函数[1],把 lambda 函数作为第一个参数值,原始元组作为第二个参数。从 F# Interactive 输出的结果元组,可以看到,函数应用到了元组的第二个元素,正是我们想要的。

在第二个版本中[2],我们使用了两种强大的技术。我们使用了散函数应用(在前一章中介绍的)来创建函数,为第二个元素加上 13195。我们没有显式写 lambda 函数,而是直接写成 (+) 13195。如果在括号中使用运算符,它的行为就像普通函数,因此,(+) 10 5 就能实现两个数的加法。如果使用散函数应用,只给它提供一个参数值,我们就获得了 int –> int 类型的函数,把这个数与任意给定的参数值相加,而这个函数的类型与 mapSecond 函数预期的一致。在这里,类型 'a -> 'b 中的'a 和'b 都替换为 int。

由于有管道,我们可以先写原始元组,然后再写应用函数。这样,代码更具可读性,首先,描述我们要操作的对象,然后,再描述做什么,就像在 C# 中,操作通常的形式是target.MethodToCall()。使用管道的另一个原因是,mapSecond的第一个参数是函数,第二个参数是元组,而没有其他的方式。

这一节,我们首先讨论 F#,因为推导高阶函数的类型签名和使用管道,在 F# 中非常自然;当然,在 C# 中,也可以使用同样的概念,我们将在下一节实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值