本次分享目的
- 让大家对 Functional Programming有一个基本的了解
- 熟悉Swift Library 中提供的Functional 式的 API,熟练应用
- 将函数式的思维运用到以后的编程工作中去
Functional is Programming Paradigms
Programming Paradigms
program paradigm.png通常将编程范式分为Imperative programming(命令式编程)和Declarative programming(声明式编程)两种;
我们比较熟悉的面相对象(OOP)就属于命令式编程范式,而我们将要介绍的函数式编程(FP)就属于声明式编程中的一种;
Swift Is Not Functional Language
通常来说,在Swift其中有var
、for loop
、while loop
等OOP类型语言的元素,所以我们更倾向于Swift是一种OOP类型语言,但是在Swift中也存在map
、filter
、reduce
等FP类型语言的API,所以它也不是纯OOP类型语言(还有POP);
Swift不是纯FP类型语言,而是一种混合类型语言,但是这不影响我们用函数式的思维方式去更好的编写Swift代码,这也是本文的目的所在;
纯FP语言,如:Haskell等
Imperative programming vs FP
下面将通过一个不太恰当的例子,来帮助大家理解Imperative programming 和 FP的区别;
问题:获得所有大于20的倍数
Imperative Style
let number = [10, 15, 20, 25, 30]
func getTargetNumber() -> [Int] {
var result = [Int]()
for i in 0..<number.count {
if number[i] > 20 {
result.append(number[i] * 2)
}
}
return result
}
let reault = getTargetNumber()
Functional style
let reault = number.filter { $0 > 20 }.map { $0 * 2 }
本质区别:
Imperative:How you are doing
FP: What you are doing
这两句真言需要大家自行领会^§^
FP Concepts
FP像OOP一样很难给出一个标准的定义,但是FP一般会有下面的几个特征:
- Modularity(模块化)
FP要求尽可能将每个程序或方法分解再分解;就像积木一样,每个方法负责一个小的、单一的功能,最终可以将这些小功能的方法组合起来形成复杂的程序;
从上面的例子中我们也可以感觉到,FP将功能进行分解,然后组合完成复杂的任务,复用性,可读性上有比较大的提高;
-
Immutability (不可变性)
FP要求不用或者尽可能少用可变的变量(var
)或属性,从而避免因为可变性,变量或属性在程序运行的过程中被更改
正是因为这个特性,所以FP在多线程情况下会更安全和高效,因为不用担心读写或者死锁等问题;
虽然不能完全避免,但是在swift编程中,我们也应尽可能的多的用let代替var,用值类型(Struct等)替代引用类型(Class);
-
Side Effects(副作用)
FP要求在方法内部不要去访问或修改方法外部的数据,其结果不受除参数外其他变量的影响,无论多少次,同样的输入要保证有同样的输出;
这个特性使得FP的方法可复用性、可测性更高;
ƒ(x)->y
我们应该牢记上面的几点,然后尽可能的应用到我们日常的编程中,哪怕不是FP编程;
First-Class and Higher-Order Functions
在FP中,functions
是一等公民,就像其他的类型(Int
、String
)一样,可以被赋值给变量,也可以作为其他方法的参数和返回值;
在Swift中我们也习惯于这样做,这也是Swift语言有Functional 的原因之一;
typealias functional = (String) -> String
func coverString1(str: String) -> String {
return str + str
}
var v1: functional = coverString1
func coverString2(str: functional) -> String {
return str("coverString2")
}
func coverString3(str: String) -> functional {
return { str1 in
return str + str1
}
}
像前面例子中用到过的,filter
、map
、coverString3
等方法,在参数中接受function或返回一个function,这类的方法称为高阶函数
下面介绍在FP语言中最普遍的几个高阶函数
注:下面几个函数,都是Swift标准库中已经存在的函数,并不需要自己来实现
Reduce
- 实现一个方法,对整数数组求和
func sum(integers: [Int]) -> Int {
var result: Int = 0
for x in integers {
result += x
}
return result
}
sum(integers: [1, 2, 3, 4]) // 10
- 实现一个方法,计算整数数组中所以元素的乘机
func product(integers: [Int]) -> Int {
var result: Int = 1
for x in integers {
result = x * result
}
return result
}
- 将字符串数组进行拼接
func concatenate(strings: [String]) -> String {
var result: String = ""
for string in strings {
result += string
}
return result
}
上面的方法都有的共同特征,首先初始化一个结果变量result
,这个结果变量的初始值不定,然后通过function
遍历输入数组中的元素,来更新result
,通用的实现如下:
extension Array {
func reduce<T>(_ initial: T, combine: (T, Element) -> T) -> T {
var result = initial
for x in self {
result = combine(result, x)
}
return result
}
}
let sum = [1, 2, 3, 4].reduce(0) { $0 + $1 }
print(sum) //10
Filter
filter
接收一个(Element) -> Bool
类型的function
作为参数,这个function
参数会遍历数组中的所有元素,然后通过返回值来决定最终结果中是否包含这个元素;
extension Array {
func filter(_ includeElement: (Element) -> Bool) -> [Element] {
var result: [Element] = []
for x in self where includeElement(x) {
result.append(x)
}
return result
}
}
let apples = ["🍎", "🍏", "🍎", "🍏", "🍏"]
let greenapples = apples.filter { $0 == "🍏"}
print(greenapples)
//[ "🍏", "🍏", "🍏"]
Map
map
接收一个(Element) -> T
类型的 function
作为参数,这个function
参数会在遍历数组中每个元素时,通过function
生成一个新的元素,并将新的元素加入到返回结果中;
extension Array {
func map<T>(_ transform: (Element) -> T) -> [T] {
var result: [T] = []
for x in self {
result.append(transform(x))
}
return result
}
}
let apples = ["🍎", "🍏", "🍎", "🍏", "🍏"]
let doubleApples = apples.map{ $0 + $0}
rint(doubleApples)
//["🍎🍎", "🍏🍏", "🍎🍎", "🍏🍏", "🍏🍏"]
Putting It All Together
下面将通过一个例子来展示上面提到的三个高阶函数组合应用的场景,例子不是特别的恰当,大家注意应用场景就好了
struct City {
let name: String
let population: Int
}
let paris = City(name: "Paris", population: 2241)
let madrid = City(name: "Madrid", population: 3165)
let amsterdam = City(name: "Amsterdam", population: 827)
let berlin = City(name: "Berlin", population: 3562)
let cities = [paris, madrid, amsterdam, berlin]
extension City {
//扩展人口显示的辅助方法
func scalingPopulation() -> City {
return City(name: name, population: population * 1000)
}
}
//大于一百万居民的城市名单列表
let result = cities
.filter { $0.population > 1000 }
.map { $0.scalingPopulation() }
.reduce("City: Population") { result, c in
return result + "\n" + "\(c.name): \(c.population)" }
print(result)
/*
City: Population
Paris: 2241000
Madrid: 3165000
Berlin: 3562000
*/
从上面的例子中更能体现FP可读性的一面
Lazy Sequences
通过前面的例子我们我们可以看到,我们可以组合式的应用这些高阶函数,来解决复杂的问题,同时使得代码更好理解及阅读;
但组合应用这些高阶函数会存性能问题:
//FP
(1...10).filter { $0 % 3 == 0 }.map { $0 * $0 }
// [9, 36, 81]
//Imperative programming
var result: [Int] = []
for element in 1...10 {
if element % 3 == 0{
result.append(element * element)
}
}
result // [9, 36, 81]
对比上面的代码我们不难发现,Imperative style会有更好的性能,因为FP在调用filter
和map
过程中进行了两次循环,同时在filter
结果传递给map
的过程中还会创建一个中间数组,这些都是性能上的损耗(虽然相对这些损耗来说,可读性更重要);
我们可以通过LazySequence
来的解决上面的问题,lazy
版如下:
let lazyResult = (1...10).lazy.filter { $0 % 3 == 0 }.map { $0 * $0 }
// let lazyResult: LazyMapSequence<LazyFilterSequence<(ClosedRange<Int>)>, Int>
Array(lazyResult)
// [9, 36, 81]
增加了lazy
关键字后,原有的两次循环操作会结合成一次操作,从而获得和Imperative style 相似的性能;
同样的应用还有下面的情况:
let first = (1...10).map{ $0 * $0 }.first!
//lazy
let first = (1...10).lazy.map{ $0 * $0 }.first!
这时候只有原数组中的第一个元素进行了 * 操作,剩下的不会被map
处理;
More
FP中还有很多其他的概念如:Functors、Monads、Applicatives等,这里就不做介绍,如果感兴趣大家可以去详细了解
Demo time
详见FunctionalDemo.playground
Demo引用自Functional Swift 第10章
When of Functional Programming
FP 在安全性、易测性、可读性等方便都有其优势,但是在我们开发应用的过程中也不是所以的地方都适合用这种FP 方式,比如Controller
中我们更多的需要和官方提供的OOP的接口去做互动,所以不是特别适合使用FP ;
比较好的应用FP的地点是在Model中,这里面更多的是处理数据及业务上的逻辑,所以能更好的发挥FP的优势;
在UI方面,RxSwfit是一中通过类FP方式实现的reactive library,大家有兴趣可以研究;
引用
Books:
Swift Functional Programming
Functional Swift
BLogs:
https://www.geeksforgeeks.org/introduction-of-programming-paradigms/
https://matteomanferdini.com/swift-functional-programming/
https://www.raywenderlich.com/9222-an-introduction-to-functional-programming-in-swift
网友评论