函数式编程(Functional Programming)是相对于我们常用的面向对象和面向过程编程的另外一种开发思维方式,它更加强调以函数为中心。善用函数式编程思路,可以对我们的开发工作有很大的帮助和启发,今天我们就来讨论一下吧。
什么是函数式编程
我们用一个简单的例子为大家说明什么是函数式编程。 比如我们有这样一个结构:
struct Staff {
var firstname: String
var lastname: String
var age: Int
var salary: Float
}
Staff
结构定义了员工的基本信息,比如姓名,年龄,薪水等等。 我们再声明一个数组,里面存放我们的员工信息:
let staffs = [
Staff(firstname:"Ming", lastname:"Zhang", age: 24, salary: 12000.0),
Staff(firstname:"Yong", lastname:"Zhang", age: 29, salary: 17000.0),
Staff(firstname:"TianCi", lastname:"Wang", age: 44, salary: 30000.0),
Staff(firstname:"Mingyu", lastname:"Hu", age: 30, salary: 15000.0),
Staff(firstname:"TianYun", lastname:"Zhang", age: 25, salary: 12000.0),
Staff(firstname:"Wang", lastname:"Meng", age: 24, salary: 14000.0)
]
行为式思维
现在,我们需要找到所有姓张的员工信息(lastname 等于 Zhang),如果用我们惯用的开发思路,可以这样解决:
var staffOfZhang = [Staff]()
for staff in staffs {
if staff.lastname == "Zhang" {
staffOfZhang.append(staff)
}
}
print(staffOfZhang)
这段代码首先声明了一个数组 staffOfZhang
用于存放符合条件的 Staff 实例,然后我们开始遍历我们的 staffs 数组,对于其中 lastname
等于 "Zhang" 的实例,将他们添加到 staffOfZhang
这个数组中。 这就完成了我们这个查找需求。
这种开发思路我们可以称作行为式思路。它侧重于告诉程序如何解决问题。比如我们定义了查询结果存放在哪里,以及如何遍历每一个实例,然后将符合条件的实例读取出来。
声明式思维
解决问题肯定不止一种方法,我们还可以换一种思维方式来解决这个问题,这种思维也可以称为声明式思维。我们可以使用 Array
的 filter
方法:
let staffOfZhang = staffs.filter { staff in
return staff.lastname == "Zhang"
}
这就是声明式思维的一个例子,这里 staffs.filter
函数接受一个闭包类型的参数,filter 方法会对 staffs 中的每一个元素都用传入 filter 的闭包调用一遍,根据这个闭包的返回值决定是否将这个元素作为符合条件(闭包的返回值如果为 true
则表示符合条件)的元素加入我们的查找结果中。
简单来说我们只需要在传入的闭包中声明好查找的规则,也就是 return staff.lastname == "Zhang"
这个表达式。这样我们就完成整个查找操作的处理了。
我们这里并没有告诉程序应该怎么去查找满足条件的元素的方法,而只是声明了一个规则。 这样做最大的好处就是能够减少我们的代码量,让我们的代码看起来非常的简洁,而且易理解。 这种方式就是函数式编程的一个例子。
First-Class function
谈到函数式编程就会提到 First Class Function。 这是个什么鬼呢~
简单来说 First Class Function 这样的函数不但可以进行简单的调用,还可以赋值给变量,也可以作为参数传递给另外一个函数,还可以作为函数的返回值。关于更详细的描述,可以参看 Wikipedia 上面的这篇文章:https://en.wikipedia.org/wiki/First-class_function
Swift 中的闭包就属于 First Class, 所以我们可以将闭包赋值给变量,传递给函数,作为返回值等等。对于我们上面的那个例子来说,我们就可以这样改写:
func filterZhang(staff:Staff) -> Bool {
return staff.lastname == "Zhang"
}
staffOfZhang = staffs.filter(filterZhang)
这正好和咱们刚才说的 First Class 对应上了。首先定义了一个 filterZhang 函数,接着将这个函数作为参数传递给 filter。
就这么简单,这也是 Swift 函数式编程的一个体现。
curry
那么,聪明的各位可能又想了,虽然我们可以定义这样一个函数,然后作为参数传递给 filter,但又有什么好处呢? 代码量并不比之前的少。
没错,这个问题问到关键之处了。且听继续分解。函数式编程的另一大特性就是 curry。这也是函数式编程的一个核心概念。
说白了 curry 就是用函数生成另一个函数。关于 curry 的详细探讨,还可以参考我之前的几篇文章:
神奇的 Currying
Swift 中 curry 特性的高级应用
下面咱们就用最简单的例子说明 curry 特性。我们可以将刚才定义的 filterZhang
方法改写一下:
func filterGenerator(lastnameCondition: String) -> (Staff) -> (Bool) {
return {staff in
return staff.lastname == lastnameCondition
}
}
那么我们来看一下 filterGenerator 的声明,首先它接收一个 String 类型的参数,然后它会返回另一个函数,这个函数接受一个 Staff 类型的参数,并且返回一个布尔值。
详细各位也都看出来了,我们定义的这个新函数 filterGenerator,其实就是对我们之前定义的 filterZhang 函数做了一个更高层的抽象。filterZhang 会过滤处所有 lastname 等于 Zhang 的员工实例。而使用 filterGenerator 可以生成任意条件的过滤函数:
let filterWang = filterGenerator("Wang")
let filterHu = filterGenerator("Hu")
大家看到了吧,我们对 filterGenerator 传入不同的参数,它就会根据这个参数生成一个新的过滤函数。然后我们就可以直接使用:
staffs.filter(filterHu)
这个调用会查询出所有 lastname 等于 Hu 的员工。这就是 curry 特性的精髓。比如,你在开发一个照片处理 APP,会对照片应用很多滤镜,并且这些滤镜还可以叠加,那么使用 curry 方式就可以让你的开发的效率和代码的健壮性提高很多。
更多应用
我们再来多看一些例子。假如我们现在想把所有员工的名字保存到另外一个数组中:
var names = [String]();
for staff in staffs {
names.append("(staff.lastname) (staff.firstname)")
}
print(names)
再来看看我们使用函数式方式如何完成:
names = staffs.map{ staff in
return "(staff.lastname) (staff.firstname)"
}
print(names)
这次我们使用的是 map 方法,它会对数组中所有的元素依照我们指定的规则进行变换,然后生成一个新的数组。这样我们只需要在闭包中声明变换规则就完成了。又是声明式思维的一种体现。
再比如,我们想计算出所有员工的平均工资:
var totalSalary = Float(0.0)
for staff in staffs {
totalSalary += staff.salary
}
print(totalSalary / Float(staffs.count))
我们再看看如何用函数式的思路来完成:
let averageSalary = staffs.reduce(0) { total, staff in
return total + staff.salary / Float(staffs.count)
}
print(averageSalary)
这次我们使用 Array 的 reduce 函数,这个函数接受两个参数,第一个参数是初始值,然后 reduce 会依次让每一个元素和这个值进行操作,然后将计算结果传递给下一个元素的调用。
对于我们这里,就是依次计算每个员工对于平均工资的基数,然后将他们相加到一起就是整体的平均工资了。我们这里依然只声明了一个规则 return total + staff.salary / Float(staffs.count)
。这样代码读起来非常的清晰,很明确的说明了我们要干什么。
结语
到这里,函数式编程的基本思路就都给大家介绍完了。总之呢函数式编程的主要特性就是声明式思维以及 curry 传递思维。它能够让我们用很优雅的语法实现在以前看来比较繁杂的逻辑,这也是它的最大优势。并且它还衍生除了一些分支,比如响应式编程,非常流行的 ReactiveCocoa 库正式 Cocoa 平台对应响应式编程的实现。这些新的编程方式希望用一种更加优雅简洁的方式来解决我们开发中的问题。