本文是一个系列,是函数式Swift的读书笔记(其实是为了备忘)
接受其它函数作为参数的函数有时被称为高阶函数。在 Swift 标准库中就有几个作用于数组的高阶函数
1. 泛型介绍
//有如下需求:让数组中元素加一
func increment(array:[Int]) -> [Int] {
var result:[Int] = []
for x in array {
result.append(x+1)
}
return result
}
// 又有如下需求
func double(array:[Int]) -> [Int] {
var result:[Int] = []
for x in array {
result.append(x*2)
}
return result
}
//上边的写法有大量的重复代码,可以用swift的写法抽象出来:
func compute(array:[Int],transform:(Int)->Int) -> [Int] {
var result:[Int] = []
for x in array{
result.append(transform(x))
}
return result
}
//那么,现在,上边的double方法的实现方式如下
func double2(array:[Int]) -> [Int] {
return compute(array: array, transform: {
$0*2
})
}
现在我们想要得到一个bool型的新数组,代码如下
func isEven(array:[Int]) -> [Bool] {
return compute(array: array, transform: {
$0 % 2 == 0
})
}
//报类型错误, 因为compose函数接受的是(Int)->Int 类型,但是我们给了(Int)->Bool类型。
// 一种方案是 定义一个新的函数
func computeBool(array:[Int],transform:(Int)->Bool) -> [Bool] {
var result:[Bool] = []
for x in array{
result.append(transform(x))
}
return result
}
//但是如果我们需要一个[String]类型的数组,又要重新定义一个函数。
// 这种问题,可以用泛型解决
func genericCompute<T>(array: [Int], transform: (Int) -> T) -> [T] {
var result: [T] = []
for x in array {
result.append(transform(x))
}
return result
}
//没有理由array数组都是[Int]类型的,所以可以继续优化
func genericCompute<Element,T>(array: [Element], transform: (Element) -> T) -> [T] {
var result: [T] = []
for x in array {
result.append(transform(x))
}
return result
}
//“这里我们写了一个 map 函数,它在两个维度都是通用的:对于任何 Element 的数组和 transform: (Element) -> T 函数,它都会生成一个 T 的新数组
实际上,比起定义一个顶层的map函数,按照swift惯例,将map函数定义为Array的扩展更好
extension Array{
func map<T>(_ transform:(Element)->T) -> [T] {
var result: [T] = []
for x in self{
result.append(transform(x))
}
return result
}
}
//不需要自己像这样来定义 map 函数,因为它已经是 Swift 标准库的一部分了 (实际上,它基于 Sequence 协议被定义,会在之后关于迭代器和序列的章节中提到)。
//本章的重点并不是说你应该自己定义 map;我们只是想要告诉你 map 的定义中并没有什么复杂难懂的魔法
2. 顶层函数与拓展
//本章开始,我们用顶层函数来作为例子展示,为了简单起见。
// 最终我们将map定义为Array的扩展,这与Swift的标准库的实现方式相似。
//随着swift的发展,协议扩展的支持,移除了使用顶层函数的方式, 我们在array这样的具体类型上定义扩展,还可以在Sequence这样的 协议上定义扩展。
// 建议遵循此规则,并把处理确定类型的函数定义为该类型的扩展。这样做的优点是自动补全更完善,有歧义的命名更少,代码结构更清晰。
3. Filter
// filter 函数接受一个函数作为参数。filter 函数的类型是 (Element) -> Bool —— 对于数组中的所有元素,此函数都会判定它是否应该被包含在结果中
extension Array{
func filter(_ includeElement:(Element)->Bool) -> [Element] {
var result: [Element] = []
for x in self where includeElement(x) {
result.append(x)
}
return result
}
}
let exampleFiles = ["readMe.md","helloworld.swift","flappybird.swift"]
func getSwiftFile(in file:[String])->[String]{
return files.filter{
file in
file.hasSuffix(".swift")
}
}
//Swift标准库中有filter,除非联系,没必要重写它。
4.Reduce
例子
func sum(integers:[Int]) -> Int {
var result: Int = 0
for x in integers {
result += x
}
return result
}
//抽向: 给一个初始值,然后便利数组更新初始值。
extension Array{
func reduce<T>(_ initial: T, combine:(T,Element)->T) -> T {
var result = initial
for x in self {
result = combine(retult,x)
}
return result
}
}
//对于任意 [Element] 类型的输入数组来说,它会计算一个类型为 T 的返回值。这么做的前提是,首先需要一个 T 类型的初始值 (赋给 result 变量),以及一个用于更新 for 循环中变量值的函数 combine: (T, Element) -> T
reduce 通过常见通用的方法体现一个常见的编程模式:便利数组并计算结果。
func sumUsingReduce(integers:[Int])->Int{
return integers.reduce(0){
result, x in
result + x
}
}
// 我们也可以使用reduce来重新定义map 和filter
extension Array{
//map
func mapUsingReduce<T>(_ transform:(Element)->T) -> [T] {
return reduce([], combine: {
result , x in
result + [transform(x)]
})
}
// filter
func filterUsingReduce(_ includeElement:(Element)->Bool) -> [Element] {
return reduce([], combine: {
result, x in
includeElement(x) ? result+[x] : result
})
}
}
5. 实际应用
struct City {
let name:String
let population:Int
}
let paras = City(name: "Paras", population: 2214)
let madrid = City(name: "Madirid", population: 3165)
let beijing = City(name: "Beijing", population: 5000)
let nanjing = City(name: "Nanjing", population: 850)
let citys = [paras,madrid,beijing,nanjing]
extension City{
func scalingPopulation() -> City {
return City(name: name, population: population*1000)
}
}
citys.filter{$0.population>1000}
.map{$0.scalingPopulation()}
.reduce("City:Population"){result,city in return result + "\n" + "\(city.name):\(city.population)"}
// 先filter, 挑选, 再map,所有的都执行scaling方法,然后在reduce,打印出来。
6. 泛型和Any类型
Any 类型和泛型两者都能用于定义接受两个不同类型参数的函数。
然而,理解两者之间的区别至关重要:泛型可以用于定义灵活的函数,类型检查仍然由编译器负责;而 Any 类型则可以避开 Swift 的类型系统 (所以应该尽可能避免使用)。
func noOp<T>(_ x:T)->T{
return x
}
func noOpAny(_ x:Any)->Any{
return x
}
//在 noOp 的定义中,我们可以清楚地看到返回值和输入值完全一样。而 noOpAny 的例子则不太一样,返回值是任意类型 — 甚至可以是和原来的输入值不同的类型。
使用泛型允许你无需牺牲类型安全就能够在编译器的帮助下写出灵活的函数