最通俗易懂的方式让你理解 Swift 的函数式编程
函数式编程(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 平台对应响应式编程的实现。这些新的编程方式希望用一种更加优雅简洁的方式来解决我们开发中的问题。
最通俗易懂的方式让你理解 Swift 的函数式编程的更多相关文章
- swift 之函数式编程(一)
1. 什么是函数式编程? 函数式编程是阿隆佐思想的在现实世界中的实现, 它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及异变物件. 函数式编程的最重要基础是λ演算.而且λ演算的函數可以接受函 ...
- swift之函数式编程
函数式编程初探 最近初学swift,和OC比,发现语言更现代,也有了更多的特性.如何写好swift代码,也许,熟练使用新特性写出更优秀的代码,就是答案.今天先从大的方向谈谈swift中的编程范式-函数 ...
- 理解iOS与函数式编程
有时候,一个关键字就是一扇通往新世界的大门.两年前,身边开始有人讨论函数式编程,拿关键字Functional Programming一搜,全是新鲜的概念和知识,顺藤摸瓜,看到的技术文章和框架也越来越多 ...
- swift之函数式编程(四)
文章内容来自<Functional Programing in Swift>,具体内容请到书中查阅 Map, Filter, Reduce Functions that take func ...
- swift之函数式编程(二)
本文的主要内容来自<Functional Programming in Swift>这本书,有点所谓的观后总结 在本书的Introduction章中: we will try to foc ...
- swift之函数式编程(五)
文章内容来源于<Functional Programing in Swift>,详情请看原著 The Value of Immutability swift 对于控制值改变有一些机制.在这 ...
- swift之函数式编程(三)
文章来源于<Functional Programing in Swift>,本系列仅仅是观后概括的一些内容 Wrapping Core Image 上一篇文章我们介绍了 高阶函数并且展示了 ...
- 深入理解 Swift 派发机制
原文: Method Dispatch in Swift作者: Brain King译者: kemchenj 译者注: 之前看了很多关于 Swift 派发机制的内容, 但感觉没有一篇能够彻底讲清楚这件 ...
- 理解Swift中map 和 flatMap对集合的作用
map和flatMap是函数式编程中常见的概念,python等语言中都有.借助于 map和flapMap 函数可以非常轻易地将数组转换成另外一个新数组. map函数可以被数组调用,它接受一个闭包作为參 ...
随机推荐
- jquery修改获取radio的选中项
<input id="txtBeginDate" onclick="$('#divDate').css({'top':$('#txtBeginDate').offs ...
- windll对象
回过头来,再看一下windll和oledll的差别,这两者之间最大的差别是oledll调用的函数都是固定返回HRESULT类型的值,而windll是不固定的类型的.在Python 3.3版本号之前,都 ...
- 逐步把Nginx及Redis引入项目组之负载均衡技术调研初版总结
本篇以一个Nginx服务.两个Tomcat服务.一个Redis搭建一个负载均衡环境,由于就一台电脑暂以随机分配client请求策略开展,详细工作中推荐以IP地址来实现client请求的动态负载策略.省 ...
- Android应用程序文件缓存getCacheDir()和getExternalCacheDir()
如果Android引用程序需要缓存临时文件,系统提供了一个可管理的“内部缓存”和一个不可管理的“外部缓存”,分别调用getCacheDir()和getExternalCacheDir()方法,可以从当 ...
- 5.7-基于Binlog+Position的复制搭建
基本环境 Master Slave MySQL版本 MySQL-5.7.16-X86_64 MySQL-5.7.16-X86_64 IP 192.168.56.156 192.168.56.157 ...
- 【hdu 1527】取石子游戏
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s) ...
- js页面加载函数
在未加载完文档,使用jquery选择器选择元素后,如果立即绑定事件进行调用,会引起js的报错(can not read property of undefined),导致事件不能绑定成功. alert ...
- hbase 从hbase上读取数据写入到hdfs
Mapper package cn.hbase.mapreduce.hb2hdfs; import java.io.IOException; import org.apache.hadoop.hbas ...
- 微信支付-公众号支付H5调用支付详解
微信公众号支付 最近项目需要微信支付,然后看了下微信公众号支付,,虽然不难,但是细节还是需要注意的,用了大半天时间写了个demo,并且完整的测试了一下支付流程,下面分享一下微信公众号支付的经验. 一. ...
- python3第一天学习(数据类型)
参考blog地址:http://www.cnblogs.com/wupeiqi/articles/5444685.html,然后根据上面知识点练习并总结. 一.数字(int) 1.数字类型说明 在 ...