Swift 函数式编程入门
是的,这真的只是入门,一点点了解而已,真正的函数式编程还差得远呢。只是初步学习了一些入门的思想,争取为以后的开发中提供一些便利。
开始
首先记一个 Swift 函数中的变化,其实也不能说是变化,因为在 Swift 3 就已经是这样了,就是函数中的参数已经不能使用 var 了,也就是函数参数全都是常量,如果希望在函数内部改变,那么只有在函数内部重新声明一个变量去接收这个参数。
func test(number: Int) -> Int {
var newNumber = number
// 对 newNumber 进行操作
return newNumber
}
我们如果想直接操作实参呢?更加复杂,需要增加 inout 关键字。
func addOne(number: inout Int) {
number = number + 1
}
var num = 5
addOne(number: &num)
num // 6
函数式编程更希望函数像是一个黑匣子一样,参数传进去,黑匣子经过一定逻辑然后返回一个结果,而并不希望我们直接操作这个参数,因此 Swift 函数参数默认都是值传递。
那么我们希望交换两个数呢?这时候肯定还是需要使用 inout 的嘛,我们定义这样一个函数。
func swapTwoInts( _ a: inout Int,_ b: inout Int) {
(a, b) = (b, a)
}
var x = 3
var y = 5
swapTwoInts(&x, &y) // 调用函数的时候,含有 inout 关键字的变量前面会有一个 &,表示地址
当然事实上,Swift 给我们提供了 swap 函数,并且是支持泛型的,也就可以交换两个字符串,两个对象,两个很多东西。我们来自己实现一下:
func swapTwo<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
var x = 3
var y = 4
swapTwo(&x, &y)
到这里还都是函数的基本使用,没有多少函数式编程的东西啊。
函数式编程入门
Swift 为什么说它支持函数式编程呢,其中一点就是它把函数列为了“一等公民”,函数可以作为变量,可以作为参数,可以作为返回值,这为函数式编程打下了基础。
函数作为变量/常量
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 中很多类似方法都是这样的,加后缀的一般都是有返回值不改变自身的)。
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 函数称为高阶函数。
我们能传入函数的地方也能传一个闭包。
numbers.sort { (num1, num2) -> Bool in
return num1 > num2
}
numbers.sort { $0 > $1 } // 参数可以使用默认的$0,$1...,由于只有一行代码,return 也可以省略
当然,sort 函数能实现的功能可不知这些,比如下面,我们可以把一个整型数组按照字符串的顺序排序:
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 操作就会把数组中的所有元素均按照这个逻辑去改变,并返回一个新的数组。
numbers.map { $0 + 1 } // 数组所有元素均加 1
numbers.map { String($0) } // 将所有元素均转成字符串
filter 函数
filter 函数,我们传入一个返回值为 Bool 的函数,所有返回值为 true 的元素会被放进一个新的数组中。
numbers.filter { $0 > 5} // 将所有大于 5 的数挑选出来组成一个新的数组
reduce 函数
reduce 函数是把数组中所有的元组组成一个值,比如数组中所有元素的和,所有元素的积,都可以使用。
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》第一篇就是讲的柯里化。
func addOne(_ num: Int) -> Int {
return num + 1
}
addOne(2)
上面的函数把一个整型加 1,可是如果我们还需要一个加 2 的函数呢,再写一个?如果需要加 3 的函数呢?再从头到尾写一个?
事实上我们可以定义一个高阶函数,把这几个函数的相同部分抽取出来,如下面:
func addTo(_ adder: Int) -> (Int) -> Int {
return { num in
return adder + num
}
}
let addFour = addTo(4)
addFour(5) // 9
柯里化其实就是把一个多参数的函数,通过抽取高阶函数,把参数的传入分层的过程。我们利用它可以做什么?如果我们有多个函数,它们拥有部分相同的逻辑,我们就可以考虑把这部分逻辑抽取出来,达到代码复用的目的。
函数作为返回值
这里使用慕课网中刘宇波老师的例子:
// 按照重量的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
}