Function Swift

本次分享目的

  • 让大家对 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 loopOOP类型语言的元素,所以我们更倾向于Swift是一种OOP类型语言,但是在Swift中也存在map、filter、reduceFP类型语言的API,所以它也不是纯OOP类型语言(还有POP);

Swift不是纯FP类型语言,而是一种混合类型语言,但是这不影响我们用函数式的思维方式去更好的编写Swift代码,这也是本文的目的所在;
纯FP语言,如:Haskell等

Imperative programming vs FP

下面将通过一个不太恰当的例子,来帮助大家理解Imperative programmingFP的区别;

问题:获得所有大于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

FPOOP一样很难给出一个标准的定义,但是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在调用filtermap过程中进行了两次循环,同时在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

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容