购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.1 泛型介绍

假如我们需要写一个函数,它接受一个给定的整型数组,通过计算得到并返回一个新数组,新数组各项为原数组中对应的整型数据加1。这一切,仅仅只需要使用一个 for 循环就能非常容易地实现:

现在假设我们还需要一个函数,用于生成一个每项都为参数数组对应项两倍的新数组。这同样能很容易地使用一个 for 循环来实现:

这两个函数有大量相同的代码,我们能不能将没有区别的地方抽象出来,并单独写一个体现这种模式并且更通用的函数呢?像这样的函数需要追加一个新参数来接受一个函数,这个参数能根据各个数组项计算得到新的整型数值:

现在,取决于我们想如何根据原数组得到一个新数组,我们可以向函数传递不同的参数。 doubleArray 函数和 incrementArray 函数都精简为一行调用 computeIntArray 的语句:

代码仍然不像想象中的那么灵活。假设我们想要得到一个布尔型的新数组,用于表示原数组中对应的数字是否为偶数,则我们可能会尝试编写一些像下面这样的代码:

不幸的是,这段代码导致了一个类型错误。问题在于,我们的 computeIntArray 函数接受一个 Int-> Int 类型的参数,也就是说,该参数是一个返回整型值的函数。而在 isEvenArray 函数的定义中,我们传递了一个 Int-> Bool 类型的参数,于是导致了类型错误。

我们应该如何解决这个问题呢?一种 可行 方案是定义新版本的 computeIntArray 函数,接受一个 Int-> Bool 类型的函数作为参数。类似下面这样:

但是,这个方案的扩展性并不好。如果接下来需要计算 String 类型呢?那么我们是否还需要定义另一个高阶函数来接受 Int-> String 类型的参数?

幸运的是,该问题有一个解决方案:我们可以使用泛型 [1] computeBoolArray computeIntArray 的定义是相同的;唯一的区别在于 类型签名(type signature) 。假如我们定义一个相似的函数 computeStringArray 来支持 String 类型,其函数体将会与先前两个函数完全一致。事实上,相同部分的代码可以用于 任何 类型。我们真正想做的是写一个能够适用于每种可能类型的泛型函数:

关于这段代码,最有意思的是它的类型签名。理解这个类型签名有助于你将 genericCompute Array<T> 理解为一个函数族。 类型 参数 T 的每个选择都会确定一个新函数。该函数接受一个整型数组和一个 Int-> T 类型的函数作为参数,并返回一个 [T] 类型的数组。

我们仍能进一步将这个函数一般化。没有理由让它仅能对类型为 [Int] 的输入数组进行处理。将数组类型进行抽象,能得到下面这样的类型签名:

这里我们写了一个 map 函数,它在两个维度都是通用的:对于任何 Element 的数组和 transform:Element-> T 函数,它都会生成一个 T 的新数组。这个 map 函数甚至比我们之前看到的 genericComputeArray 函数更通用。事实上,我们可以通过 map 来定义 genericComputeArray

同样地,上述函数的定义并没有什么太过特别之处:函数接受 xs transform 两个参数之后,将它们传递给 map 函数,然后返回结果。关于这个定义,最有意思的非类型莫属。 genericComputeArray(_:transform:) map 函数的一个实例,只是它有一个更具体的类型。

实际上,比起定义一个顶层 map 函数,按照Swift的惯例将 map 定义为 Array 的扩展会更合适:

我们在函数的 transform 参数中所使用的 Element 类型源自于Swift的 Array 中对 Element 所进行的泛型定义。

作为 map(xs,transform) 的替代,我们现在可以通过 xs.map(transform) 来调用 Array map 函数:

想必你会很乐意听到其实并不需要自己像这样来定义 map 函数,因为它已经是Swift标准库的一部分了(实际上,它基于 SequenceType 协议被定义,我们会在之后第10章中提到)。本章的重点 并不是 说你应该自己定义 map ;我们只是想要告诉你 map 的定义中并没有什么复杂难懂的魔法——你 能够 轻松地自己定义它!

顶层函数和扩展

你可能已经注意到,在本节的函数中我们使用了两种不同的方式来声明函数:顶层函数和类型扩展。在一开始创建 map 函数的过程中,为了简单起见,我们选择了顶层函数的版本作为例子进行展示。不过,最终我们将 map 的泛型版本定义为 Array 的扩展,这与它在Swift标准库中的实现方式十分相似。

在Swift标准库最初的版本中,顶层函数仍然是无处不在的,但伴随Swift 2.0的诞生,这种模式被彻底地从标准库中移除了。随着协议扩展(protocol extensions),当前第三方开发者有了一个强有力的工具来定义他们自己的扩展——现在我们不仅仅可以在 Array 这样的具体类型上进行定义,还可以在 SequenceType 一样的协议上来定义扩展。

我们建议遵循此规则,并把处理确定类型的函数定义为该类型的扩展。这样做的优点是,自动补全更完善、有歧义的命名更少,以及(通常)代码结构更清晰。 ds2iF9bLoJmqWjGCICh0ol/e0zDX0utja2jaKYz6vH0c0JrSAlo7+/TXYMFVXhBX

点击中间区域
呼出菜单
上一章
目录
下一章
×