最通俗易懂的方式让你理解 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函数可以被数组调用,它接受一个闭包作为參 ...
随机推荐
- ConcurrentLinkedQueue使用方法
它是一个基于链接节点的无界线程安全队列.该队列的元素遵循先进先出的原则.头是最先加入的,尾是最近加入的. 插入元素是追加到尾上.提取一个元素是从头提取.当多个线程共享访问一个公共 collection ...
- ConcurrentHashMap 内部实现分析
ConcurrentHashMap ConcurrentHashMap是一个线程安全的Hash Table,它的主要功能是提供了一组和HashTable功能相同但是线程安全的方法.Concurrent ...
- php面试题7(1、unset变量是删除栈变量,并不删除堆变量)(2、php爬虫特别简单: 可以file_get_contents和直接fopen)
php面试题7(1.unset变量是删除栈变量,并不删除堆变量)(2.php爬虫特别简单: 可以file_get_contents和直接fopen) 一.总结 1.unset变量是删除栈变量,并不删除 ...
- 【34.25%】【BZOJ 2648】SJY摆棋子
Time Limit: 20 Sec Memory Limit: 128 MB Submit: 2718 Solved: 931 [Submit][Status][Discuss] Descrip ...
- Android 用LinkedList实现队列
队列 队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作.进行插入操作的端称为队尾,进行删除操作的端称为队头.队列中没有元素时,称为空队列. 在 ...
- NOIP 模拟 路径求和 - Tarjan+dfs+码
题目大意: 各一个奇环内向森林,求每两个点对间的距离之和.无法到达则距离为-1. 分析: 首先Tarjan找出size大于1的连通分量(环),环中的边的贡献可以单独计算. 然后从入度为0的点向内dfs ...
- System.ArgumentException: 已添加了具有相同键的项。(An item with the same key has already been added) 在 System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add) 在 System.Web.Mvc.Js
最近将一个项目从ASP.NET MVC 3升级至刚刚发布的ASP.NET MVC 5.1,升级后发现一个ajax请求出现了500错误,日志中记录的详细异常信息如下: System.ArgumentEx ...
- 给自己的java程序生成API帮助文档
一.问题发现: 课本上提到"要学会给自己编写的程序生成API帮助文档",但又没有说明具体的操作步骤. 二.分析: API帮助文档有什么用?这么理解吧:如果想告诉别人你的类如何使用, ...
- Android Activity 悬浮 半透明边框
1.首先来创建一个Activity,在Activity的OnCreate函数里面我们设置它为全屏,然后设置Activity的宽高为全屏*0.9,然后设置背景图片为半透明的 .9 图片 .这样就已经是非 ...
- sql server中触发器
触发器是一种特殊类型的存储过程,它不同于之前的我们介绍的存储过程.触发器主要是通过事件进行触发被自动调用执行的.而存储过程可以通过存储过程的名称被调用. Ø 什么是触发器 触发器对表进行插入.更新.删 ...