Function Swift

本次分享目的

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

Functional is Programming Paradigms

Programming Paradigms
program paradigm.png

通常將編程范式分為Imperative programming(命令式編程)和Declarative programming(聲明式編程)兩種;

我們比較熟悉的面相對象(OOP)就屬于命令式編程范式,而我們將要介紹的函數(shù)式編程(FP)就屬于聲明式編程中的一種;

Swift Is Not Functional Language

通常來說,在Swift其中有var 、for loop、while loopOOP類型語言的元素,所以我們更傾向于Swift是一種OOP類型語言,但是在Swift中也存在mapfilter、reduceFP類型語言的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

FPOOP一樣很難給出一個標準的定義,但是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
    }
}

像前面例子中用到過的,filtermap、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在調用filtermap過程中進行了兩次循環(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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容