本次分享目的
- 讓大家對 Functional Programming有一個基本的了解
- 熟悉Swift Library 中提供的Functional 式的 API,熟練應用
- 將函數(shù)式的思維運用到以后的編程工作中去
Functional is Programming Paradigms
Programming Paradigms

通常將編程范式分為Imperative programming(命令式編程)和Declarative programming(聲明式編程)兩種;
我們比較熟悉的面相對象(OOP)就屬于命令式編程范式,而我們將要介紹的函數(shù)式編程(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類型語言,而是一種混合類型語言,但是這不影響我們用函數(shù)式的思維方式去更好的編寫Swift代碼,這也是本文的目的所在;
純FP語言,如:Haskell等
Imperative programming vs FP
下面將通過一個不太恰當?shù)睦樱瑏韼椭蠹依斫?strong>Imperative programming 和 FP的區(qū)別;
問題:獲得所有大于20的倍數(shù)
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 }
本質區(qū)別:
Imperative:How you are doing
FP: What you are doing
這兩句真言需要大家自行領會^§^
FP Concepts
FP像OOP一樣很難給出一個標準的定義,但是FP一般會有下面的幾個特征:
- Modularity(模塊化)
FP要求盡可能將每個程序或方法分解再分解;就像積木一樣,每個方法負責一個小的、單一的功能,最終可以將這些小功能的方法組合起來形成復雜的程序;
從上面的例子中我們也可以感覺到,F(xiàn)P將功能進行分解,然后組合完成復雜的任務,復用性,可讀性上有比較大的提高;
Immutability (不可變性)
FP要求不用或者盡可能少用可變的變量(var)或屬性,從而避免因為可變性,變量或屬性在程序運行的過程中被更改
正是因為這個特性,所以FP在多線程情況下會更安全和高效,因為不用擔心讀寫或者死鎖等問題;
雖然不能完全避免,但是在swift編程中,我們也應盡可能的多的用let代替var,用值類型(Struct等)替代引用類型(Class);Side Effects(副作用)
FP要求在方法內部不要去訪問或修改方法外部的數(shù)據(jù),其結果不受除參數(shù)外其他變量的影響,無論多少次,同樣的輸入要保證有同樣的輸出;
這個特性使得FP的方法可復用性、可測性更高;
?(x)->y
我們應該牢記上面的幾點,然后盡可能的應用到我們日常的編程中,哪怕不是FP編程;
First-Class and Higher-Order Functions
在FP中,functions 是一等公民,就像其他的類型(Int、String)一樣,可以被賦值給變量,也可以作為其他方法的參數(shù)和返回值;
在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等方法,在參數(shù)中接受function或返回一個function,這類的方法稱為高階函數(shù)
下面介紹在FP語言中最普遍的幾個高階函數(shù)
注:下面幾個函數(shù),都是Swift標準庫中已經存在的函數(shù),并不需要自己來實現(xiàn)
Reduce
- 實現(xiàn)一個方法,對整數(shù)數(shù)組求和
func sum(integers: [Int]) -> Int {
var result: Int = 0
for x in integers {
result += x
}
return result
}
sum(integers: [1, 2, 3, 4]) // 10
- 實現(xiàn)一個方法,計算整數(shù)數(shù)組中所以元素的乘機
func product(integers: [Int]) -> Int {
var result: Int = 1
for x in integers {
result = x * result
}
return result
}
- 將字符串數(shù)組進行拼接
func concatenate(strings: [String]) -> String {
var result: String = ""
for string in strings {
result += string
}
return result
}
上面的方法都有的共同特征,首先初始化一個結果變量result,這個結果變量的初始值不定,然后通過function遍歷輸入數(shù)組中的元素,來更新result,通用的實現(xiàn)如下:
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作為參數(shù),這個function參數(shù)會遍歷數(shù)組中的所有元素,然后通過返回值來決定最終結果中是否包含這個元素;
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作為參數(shù),這個function參數(shù)會在遍歷數(shù)組中每個元素時,通過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
下面將通過一個例子來展示上面提到的三個高階函數(shù)組合應用的場景,例子不是特別的恰當,大家注意應用場景就好了
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
*/
從上面的例子中更能體現(xiàn)FP可讀性的一面
Lazy Sequences
通過前面的例子我們我們可以看到,我們可以組合式的應用這些高階函數(shù),來解決復雜的問題,同時使得代碼更好理解及閱讀;
但組合應用這些高階函數(shù)會存性能問題:
//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]
對比上面的代碼我們不難發(fā)現(xiàn),Imperative style會有更好的性能,因為FP在調用filter和map過程中進行了兩次循環(huán),同時在filter結果傳遞給map的過程中還會創(chuàng)建一個中間數(shù)組,這些都是性能上的損耗(雖然相對這些損耗來說,可讀性更重要);
我們可以通過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 關鍵字后,原有的兩次循環(huán)操作會結合成一次操作,從而獲得和Imperative style 相似的性能;
同樣的應用還有下面的情況:
let first = (1...10).map{ $0 * $0 }.first!
//lazy
let first = (1...10).lazy.map{ $0 * $0 }.first!
這時候只有原數(shù)組中的第一個元素進行了 * 操作,剩下的不會被map處理;
More
FP中還有很多其他的概念如:Functors、Monads、Applicatives等,這里就不做介紹,如果感興趣大家可以去詳細了解
Demo time
詳見FunctionalDemo.playground
Demo引用自Functional Swift 第10章
When of Functional Programming
FP 在安全性、易測性、可讀性等方便都有其優(yōu)勢,但是在我們開發(fā)應用的過程中也不是所以的地方都適合用這種FP 方式,比如Controller中我們更多的需要和官方提供的OOP的接口去做互動,所以不是特別適合使用FP ;
比較好的應用FP的地點是在Model中,這里面更多的是處理數(shù)據(jù)及業(yè)務上的邏輯,所以能更好的發(fā)揮FP的優(yōu)勢;
在UI方面,RxSwfit是一中通過類FP方式實現(xiàn)的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