0%

Swift 函数式编程入门

是的,这真的只是入门,一点点了解而已,真正的函数式编程还差得远呢。只是初步学习了一些入门的思想,争取为以后的开发中提供一些便利。

开始

首先记一个 Swift 函数中的变化,其实也不能说是变化,因为在 Swift 3 就已经是这样了,就是函数中的参数已经不能使用 var 了,也就是函数参数全都是常量,如果希望在函数内部改变,那么只有在函数内部重新声明一个变量去接收这个参数。

1
2
3
4
5
6
7
func test(number: Int) -> Int {

var newNumber = number

// 对 newNumber 进行操作
return newNumber
}

我们如果想直接操作实参呢?更加复杂,需要增加 inout 关键字。

1
2
3
4
5
6
7
func addOne(number: inout Int) {
number = number + 1
}

var num = 5
addOne(number: &num)
num // 6

函数式编程更希望函数像是一个黑匣子一样,参数传进去,黑匣子经过一定逻辑然后返回一个结果,而并不希望我们直接操作这个参数,因此 Swift 函数参数默认都是值传递。

那么我们希望交换两个数呢?这时候肯定还是需要使用 inout 的嘛,我们定义这样一个函数。

1
2
3
4
5
6
7
8
func swapTwoInts( _ a: inout Int,_ b: inout Int) {
(a, b) = (b, a)
}

var x = 3
var y = 5

swapTwoInts(&x, &y) // 调用函数的时候,含有 inout 关键字的变量前面会有一个 &,表示地址

当然事实上,Swift 给我们提供了 swap 函数,并且是支持泛型的,也就可以交换两个字符串,两个对象,两个很多东西。我们来自己实现一下:

1
2
3
4
5
6
7
8
func swapTwo<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}

var x = 3
var y = 4

swapTwo(&x, &y)

到这里还都是函数的基本使用,没有多少函数式编程的东西啊。

函数式编程入门

Swift 为什么说它支持函数式编程呢,其中一点就是它把函数列为了“一等公民”,函数可以作为变量,可以作为参数,可以作为返回值,这为函数式编程打下了基础。

函数作为变量/常量

1
2
3
4
5
6
7
8
func add(num1 a: Int, num2 b: Int) -> Int {
return a + b
}

let add2 = add // 这里直接将函数名赋值给一个常量

add(num1: 3, num2: 5) // 8
add2(3, 5) // 8

我们把一个函数名直接赋值给了一个常量,此时 add2 的类型为 (Int, Int) -> Int,也就是说这个变量是一个函数类型的,之后我们使用 add2(3, 5) 的方式依然可以调用这个函数(只不过没有了参数名)。

函数作为参数

我们都熟悉 Objective-C 中的 Block,而 Swift 中的 Closure 其实和 Block 是一回事儿,都是匿名函数。Swift 由于函数可以作为变量,我们原来传入闭包的地方现在传入一个函数变量是完全没问题的。

sort 函数

比如 Swift 中的 sort 函数(sorted() 有返回值,不改变自身,sort() 返回类型为 Void,改变自身;事实上 Swift 中很多类似方法都是这样的,加后缀的一般都是有返回值不改变自身的)。

1
2
3
4
5
6
7
8
9
var numbers = [4, 7, 2, 1, 7, 12, 3]

func bigFirst(a: Int, b: Int) -> Bool {
return a > b
}

numbers.sort(by: bigFirst) // [12, 7, 7, 4, 3, 2, 1]

// numbers.sort(by: >) // > 本质上就是一个函数,因此这样也是可以的

上面就是函数作为参数的一个例子,这里的 sort 函数称为高阶函数。

我们能传入函数的地方也能传一个闭包。

1
2
3
4
numbers.sort { (num1, num2) -> Bool in
return num1 > num2
}
numbers.sort { $0 > $1 } // 参数可以使用默认的$0,$1...,由于只有一行代码,return 也可以省略

当然,sort 函数能实现的功能可不知这些,比如下面,我们可以把一个整型数组按照字符串的顺序排序:

1
2
3
var numbers = [123, 110, 1, 23, 235, 20, 315]

numbers.sort { String($0) < String($1) } // [1, 110, 123, 20, 23, 235, 315]

下面介绍所有支持函数式编程的语言都基本会有的三个函数,那就是著名的 map, filter 和 reduce 函数。

map 函数

map 函数作用在数组上,我们传入一个函数,告诉这个数组每一个元素应该按照什么样的逻辑发生改变,那么 map 操作就会把数组中的所有元素均按照这个逻辑去改变,并返回一个新的数组。

1
2
numbers.map { $0 + 1 } // 数组所有元素均加 1
numbers.map { String($0) } // 将所有元素均转成字符串

filter 函数

filter 函数,我们传入一个返回值为 Bool 的函数,所有返回值为 true 的元素会被放进一个新的数组中。

1
numbers.filter { $0 > 5} // 将所有大于 5 的数挑选出来组成一个新的数组

reduce 函数

reduce 函数是把数组中所有的元组组成一个值,比如数组中所有元素的和,所有元素的积,都可以使用。

1
2
3
4
var numbers = [4, 7, 2, 1, 7, 12, 3]

numbers.reduce(0, +) // 所有元素的和
numbers.reduce("") { $0 + String($1) + " " } // 返回 "4 7 2 1 7 12 3 "这样一个字符串

柯里化

王巍的《Swifter - Swift 必备 Tips》第一篇就是讲的柯里化。

1
2
3
4
5
func addOne(_ num: Int) -> Int {
return num + 1
}

addOne(2)

上面的函数把一个整型加 1,可是如果我们还需要一个加 2 的函数呢,再写一个?如果需要加 3 的函数呢?再从头到尾写一个?

事实上我们可以定义一个高阶函数,把这几个函数的相同部分抽取出来,如下面:

1
2
3
4
5
6
7
8
9
func addTo(_ adder: Int) -> (Int) -> Int {
return { num in
return adder + num
}
}

let addFour = addTo(4)

addFour(5) // 9

柯里化其实就是把一个多参数的函数,通过抽取高阶函数,把参数的传入分层的过程。我们利用它可以做什么?如果我们有多个函数,它们拥有部分相同的逻辑,我们就可以考虑把这部分逻辑抽取出来,达到代码复用的目的。

函数作为返回值

这里使用慕课网中刘宇波老师的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 按照重量的1倍计算邮费
func tier1MailFeeByWeight(weight: Int) -> Int {
return 1 * weight
}

// 按照重量的3倍计算邮费
func tier2MainFeeByWeight(weight: Int) -> Int {
return 3 * weight
}

// 根据重量的不同,选择不同的邮费计算方式,当重量小于等于10的时候选择1倍计算邮费,否则选择3倍计算邮费
func chooseMailFeeCalculationByWeight(weight: Int) -> (Int) -> Int {
return weight <= 10 ? tier1MailFeeByWeight : tier2MainFeeByWeight
}

func feeByUnitPrice(price: Int, weight: Int) -> Int {

// 这里根据参数的不同,可以得到不同的函数作为返回值
let mailFeeByWeight = chooseMailFeeCalculationByWeight(weight: weight)
return mailFeeByWeight(weight) + price * weight

}