泛型(完整代码见文末)
当两个函数的主体逻辑即定义相同,只是参数/返回值的类型不同时,即类型签名(type signature)不同,可以使用泛型以使函数可以适用于每种可能的类型
逻辑部分: 有一个 Int 类型的数组 intArray,数组中的每个元素经过变形后,被添加到 result 数组中, 其中每个元素变形后的类型不确定,可能是 Int 或者 String 等,这里使用泛型 T 代表变形后的元素类型,以及 result 数组的元素类型。
关于这段代码,最有意思的是它的类型签名。理解这个类型签名有助于你将 genericComputeArray<T> 理解为一个函数族。类型参数 T 的每个选择都会确定一个新函数。 该函数接受一个整型数组和一个 (Int) -> T 类型的函数作为参数,并返回一个 [T] 类型的数组。
func genericComputeArray1<T>(intArray: [Int], transform: (Int) -> T) -> [T] {
var result: [T] = []
for x in intArray {
result.append(transform(x))
}
return result
}
Map
再对数组进行抽象,数组的元素可以为任何类型,不一定是 Int
func map<Element, T>(anyArray: [Element], transform: (Element) -> T) -> [T] {
var result: [T] = []
for element in anyArray {
result.append(transform(element))
}
return result
}
这里我们写了一个 map 函数,它在两个维度都是通用的:对于任何 Element 类型的数组和 transform: (Element) -> T 函数,它都会生成一个 T 类型的新数组。这个 map 函数比我们之前看到的 genericComputeArray 函数更通用。
我们在函数的 transform 参数中所使用的 Element 类型源自于 Swift 的 Array 中对 Element 所进行的泛型定义。
// 实际上,比起定义一个顶层 map 函数,按照 Swift 的惯例将 map 定义为 Array 的扩展会更合适:
extension Array{
func mapCustom<T>(transform: (Element) -> T) -> [T] {
var result: [T] = []
for element in self {
result.append(transform(element))
}
return result
}
}
[1,3].mapCustom { (element) -> Int in
element + 1
}
我们建议遵循此规则,并把处理确定类型的函数定义为该类型的扩展。这样做的优点是自动补全更完善,暧昧的命名更少,以及 (通常) 代码结构更清晰。
Filter
假设我们有一个由字符串组成的数组,代表文件夹的内容。现在如果我们想要一个包含所有 .swift 文件的数组,可以很容易通过简单的循环得到:
let exampleFiles = ["README.md", "HelloWorld.swift", "FlappyBird.swift"]
func getSwiftFiles(files: [String]) -> [String] {
var result: [String] = []
for file in files {
if file.hasSuffix(".swift") {
result.append(file)
}
}
return result
}
// 使用这个函数来取得 exampleFiles 数组中的 Swift 文件:
getSwiftFiles(files: exampleFiles) // ["HelloWorld.swift", "FlappyBird.swift"]
我们可以定义一个名为 Filter 的通用型函数。就像之前看到的 map 那样, Filter 函数接受一个函数作为参数。 Filter 函数的类型是 (Element) -> Bool —— 对于数 组中的所有元素,此函数都会判定它是否应该被包含在结果中:
extension Array {
func filterCustom(isIncludeElement: (Element) -> Bool) -> [Element] {
var result: [Element] = []
for element in self {
if isIncludeElement(element) {
result.append(element)
}
}
return result
}
}
let exampleResult = ["README.md", "HelloWorld.swift", "FlappyBird.swift"].filterCustom { (element) -> Bool in
element.hasSuffix(".swift")
}
Reduce
// 定义一个计算 Int 类型数组中所有元素之和的函数:
func sumCustom(intArray: [Int]) -> Int {
var result: Int = 0
for number in intArray {
result += number
}
return result
}
sumCustom(intArray: [1, 3, 4]) // 8
// 定义一个计算 Int 类型数组中所有元素之积的函数:
func produceCustom(intArray: [Int]) -> Int {
var result: Int = 1
for number in intArray {
result *= number
}
return result
}
produceCustom(intArray: [1, 3, 4]) // 12
这些函数有什么共同点呢?它们都将变量 result 初始化为某个值。随后对输入数组 intArray 的每一项进行遍历,最后以某种方式更新 result 。为了定义一个可以体现所需类型的泛型函数,我们需要对两份信息进行抽象:赋给 result 变量的初始值,和用于在每一次循环中更新 result 的函数。
extension Array {
func reduceCustom<T>(initial: T, combine:(T, Element) -> T) -> T {
var result = initial
for element in self {
result = combine(result, element)
}
return result
}
}
[1,3,4].reduceCustom(initial: 0) { (initial, element) -> Int in initial + element } // 8
[1,3,4].reduceCustom(initial: 1) { (initial, element) -> Int in initial * element } // 12
[1,3,4].reduceCustom(initial: 0, combine: +) // 8
[1,3,4].reduceCustom(initial: 1, combine: *) // 12
泛型和 Any 类型
除了泛型,Swift 还支持 Any 类型,它能代表任何类型的值。从表面上看,这好像和泛型极其相似。Any 类型和泛型两者都能用于定义接受两个不同类型参数的函数。然而,理解两者之间 的区别至关重要:泛型可以用于定义灵活的函数,类型检查仍然由编译器负责;而 Any 类型则 可以避开 Swift 的类型系统 (所以应该尽可能避免使用)。
func noOpt<T>(x: T) -> T {
return x
}
func noOptAny(x: Any) -> Any {
return x
}
func noOpAnyWrong(x: Any) -> Any {
return 0 // Error: 会报错
}
完整代码
import Foundation
/* 泛型 */
//当两个函数的主体逻辑即定义相同,只是参数/返回值的类型不同时,即类型签名(type signature)不同,可以使用泛型以使函数可以适用于每种可能的类型
//逻辑部分: 有一个 Int 类型的数组 intArray,数组中的每个元素经过变形后,被添加到 result 数组中, 其中每个元素变形后的类型不确定,可能是 Int 或者 String 等,这里使用泛型 T 代表变形后的元素类型,以及 result 数组的元素类型。
// 关于这段代码,最有意思的是它的类型签名。理解这个类型签名有助于你将 genericComputeArray<T> 理解为一个函数族。类型参数 T 的每个选择都会确定一个新函数。 该函数接受一个整型数组和一个 (Int) -> T 类型的函数作为参数,并返回一个 [T] 类型的数组。
func genericComputeArray1<T>(intArray: [Int], transform: (Int) -> T) -> [T] {
var result: [T] = []
for x in intArray {
result.append(transform(x))
}
return result
}
/* Map */
//再对数组进行抽象,数组的元素可以为任何类型,不一定是 Int
func map<Element, T>(anyArray: [Element], transform: (Element) -> T) -> [T] {
var result: [T] = []
for element in anyArray {
result.append(transform(element))
}
return result
}
//这里我们写了一个 map 函数,它在两个维度都是通用的:对于任何 Element 类型的数组和 transform: (Element) -> T 函数,它都会生成一个 T 类型的新数组。这个 map 函数比我们之前看到的 genericComputeArray 函数更通用。
//我们在函数的 transform 参数中所使用的 Element 类型源自于 Swift 的 Array 中对 Element 所进行的泛型定义。
// 实际上,比起定义一个顶层 map 函数,按照 Swift 的惯例将 map 定义为 Array 的扩展会更合适:
extension Array{
func mapCustom<T>(transform: (Element) -> T) -> [T] {
var result: [T] = []
for element in self {
result.append(transform(element))
}
return result
}
}
[1,3].mapCustom { (element) -> Int in
element + 1
}
// 我们建议遵循此规则,并把处理确定类型的函数定义为该类型的扩展。这样做的优点是自动补全更完善,暧昧的命名更少,以及 (通常) 代码结构更清晰。
/* Filter */
// 假设我们有一个由字符串组成的数组,代表文件夹的内容。现在如果我们想要一个包含所有 .swift 文件的数组,可以很容易通过简单的循环得到:
let exampleFiles = ["README.md", "HelloWorld.swift", "FlappyBird.swift"]
func getSwiftFiles(files: [String]) -> [String] {
var result: [String] = []
for file in files {
if file.hasSuffix(".swift") {
result.append(file)
}
}
return result
}
// 使用这个函数来取得 exampleFiles 数组中的 Swift 文件:
getSwiftFiles(files: exampleFiles) // ["HelloWorld.swift", "FlappyBird.swift"]
// 我们可以定义一个名为 Filter 的通用型函数。就像之前看到的 map 那样, Filter 函数接受一个函数作为参数。 Filter 函数的类型是 (Element) -> Bool —— 对于数 组中的所有元素,此函数都会判定它是否应该被包含在结果中:
extension Array {
func filterCustom(isIncludeElement: (Element) -> Bool) -> [Element] {
var result: [Element] = []
for element in self {
if isIncludeElement(element) {
result.append(element)
}
}
return result
}
}
let exampleResult = ["README.md", "HelloWorld.swift", "FlappyBird.swift"].filterCustom { (element) -> Bool in
element.hasSuffix(".swift")
}
/* Reduce */
// 定义一个计算 Int 类型数组中所有元素之和的函数:
func sumCustom(intArray: [Int]) -> Int {
var result: Int = 0
for number in intArray {
result += number
}
return result
}
sumCustom(intArray: [1, 3, 4]) // 8
// 定义一个计算 Int 类型数组中所有元素之积的函数:
func produceCustom(intArray: [Int]) -> Int {
var result: Int = 1
for number in intArray {
result *= number
}
return result
}
produceCustom(intArray: [1, 3, 4]) // 12
// 这些函数有什么共同点呢?它们都将变量 result 初始化为某个值。随后对输入数组 intArray 的每一项进行遍历,最后以某种方式更新 result 。为了定义一个可以体现所需类型的泛型函数,我们需要对两份信息进行抽象:赋给 result 变量的初始值,和用于在每一次循环中更新 result 的函数。
extension Array {
func reduceCustom<T>(initial: T, combine:(T, Element) -> T) -> T {
var result = initial
for element in self {
result = combine(result, element)
}
return result
}
}
[1,3,4].reduceCustom(initial: 0) { (initial, element) -> Int in initial + element } // 8
[1,3,4].reduceCustom(initial: 1) { (initial, element) -> Int in initial * element } // 12
[1,3,4].reduceCustom(initial: 0, combine: +) // 8
[1,3,4].reduceCustom(initial: 1, combine: *) // 12
/* 泛型和 Any 类型*/
//除了泛型,Swift 还支持 Any 类型,它能代表任何类型的值。从表面上看,这好像和泛型极其相似。Any 类型和泛型两者都能用于定义接受两个不同类型参数的函数。然而,理解两者之间 的区别至关重要:泛型可以用于定义灵活的函数,类型检查仍然由编译器负责;而 Any 类型则 可以避开 Swift 的类型系统 (所以应该尽可能避免使用)。
func noOpt<T>(x: T) -> T {
return x
}
func noOptAny(x: Any) -> Any {
return x
}
func noOpAnyWrong(x: Any) -> Any {
return 0 // Error: 会报错
}
// ___
// //|||\\
// //|-_-|\\
// |||
// |
// |
// / \