函数式编程(Funtional Programming)
基本概念
函数式编程(Funtional Programming,简称FP)是一种编程范式,也就是如何编写程序的方法论
- 主要思想:把计算过程尽量分解成一系列可复用函数的调用
- 主要特征:”函数的第一等公民“,函数与其他数据类型一样的地位,可以赋值给其他变量,也可以作为函数参数、函数返回值
函数式编程最早出现在LISP
语言,绝大部分的现代编程语言也对函数式编程做了不同程度的支持,比如Haskell、JavaScript、Swift、Python、Kotlin、Scala
等
函数式编程中几个常用概念
- Higher-Order Function、Function Currying
- Functor、Applicative Functor、Monad
参考资料:
adit.io/posts/2013-… mokacoding.com/blog/functo…
Array的常见操作
var array = [1, 2, 3, 4]
// map:遍历数组,可以将每个元素对应做调整变成新的元素,放入新的数组中
var array2 = array.map { $0 * 2 } // [2, 4, 6, 8]
// filter:遍历数组,选出符合条件的元素放入新的数组中
var array3 = array.filter { $0 % 2 == 0 }
// reduce:首先设定一个初始值(0)
// $0:上一次遍历返回的结果(0,1,3,10)
//$1:每次遍历到的数组元素(1,2,3,4)
var array4 = array.reduce(0) { $0 + $1 } // 10
var array5 = array.reduce(0, +) // 同array4一样
var array = [1, 2, 3, 4]
func double(_ i: Int) -> Int { i * 2 }
print(array.map(double)) // [2, 4, 6, 8]
print(array.map { double($0) }) // [2, 4, 6, 8]
map
和flatMap、compactMap
的区别
var arr = [1, 2, 3]
var arr2 = arr.map { Array(repeating: $0, count: $0) } // [[1], [2, 2], [3, 3, 3]]
// flatMap会将处理完的新元素都放在同一个数组中
var arr3 = arr.flatMap { Array(repeating: $0, count: $0) } // [1, 2, 2, 3, 3, 3]
var arr = ["123", "test", "jack", "-30"]
var arr1 = arr.map { Int($0) } // [Optional(123), nil, nil, Optional(-30)]
var arr2 = arr.compactMap { Int($0) } // [123, -30]
var arr3 = arr.flatMap(Int.init)
使用reduce
分别实现map、filter
功能
var arr = [1, 2, 3, 4]
// map
var arr1 = arr.map { $0 * 2 }
print(arr1)
var arr2 = arr.reduce([]) { $0 + [$1 * 2] }
print(arr1)
// filter
var arr3 = arr.filter { $0 % 2 == 0 }
print(arr3)
var arr4 = arr.reduce([]) { $1 % 2 == 0 ? $0 + [$1] : $0 }
print(arr4)
lazy
的优化
let arr = [1, 2, 3]
let result = arr.lazy.map { (i: Int) -> Int in
print("mapping \(i)")
return i * 2
}
print("begin-----")
print("mapped", result[0])
print("mapped", result[1])
print("mapped", result[2])
print("end-----")
//begin-----
//mapping 1
//mapped 2
//mapping 2
//mapped 4
//mapping 3
//mapped 6
//end-----
Optional
的map
和flatMap
会先将可选类型解包,处理完会再进行包装返回出去
var num1: Int? = 10
var num2 = num1.map { $0 * 2 } // Optional(20)
var num3: Int? = nil
var num4 = num3.map { $0 * 2 } // nil
var num1: Int? = 10
var num2 = num1.map { Optional.some($0 * 2) } // Optional(Optional(20))
//flatMap发现其为可选项,不会再进行包装
var num3 = num1.flatMap { Optional.some($0 * 2) } // Optional(20)
var num4 = num1.flatMap { $0 * 2 } // Optional(20)
var num1: Int? = 10
var num2 = (num1 != nil) ? (num1! + 10) : nil // Optional(20)
var num3 = num1.map { $0 + 10 } // Optional(20)
var fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd"
var str: String? = "2011-09-10"
var date1 = str != nil ? fmt.date(from: str!) : nil // Optional(2011-09-09 16:00:00 +0000)
var date2 = str.flatMap(fmt.date) // Optional(2011-09-09 16:00:00 +0000)
var score: Int? = 98
var str1 = score != nil ? "score is \(score!)" : "No score" // score is 98
var str2 = score.map { "score is \($0)"} ?? "No score" // score is 98
struct Person {
var name: String
var age: Int
}
var items = [
Person(name: "jack", age: 20),
Person(name: "rose", age: 21),
Person(name: "kate", age: 22)
]
func getPerson1(_ name: String) -> Person? {
// 遍历数组找到对应的索引
let index = items.firstIndex { $0.name == name }
return index != nil ? items[index!] : nil
}
func getPerson2(_ name: String) -> Person? {
items.firstIndex { $0.name == name }
.map { items[$0] }
}
let p1 = getPerson1("rose")
let p2 = getPerson2("rose")
struct Person {
var name: String
var age: Int
init?(_ json: [String : Any]) {
guard let name = json["name"] as? String,
let age = json["age"] as? Int else { return nil }
self.name = name
self.age = age
}
}
var json: Dictionary? = ["name" : "Jack", "age" : 10]
var p1 = json != nil ? Person(json!) : nil // Optional(__lldb_expr_36.Person(name: "Jack", age: 10))
var p2 = json.flatMap(Person.init) // Optional(__lldb_expr_36.Person(name: "Jack", age: 10))
函数式的写法
假如要实现以下功能: [(num + 3) * 5 - 1] % 10 / 2
传统写法
var num = 1
func add(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
func sub(_ v1: Int, _ v2: Int) -> Int { v1 - v2 }
func multiple(_ v1: Int, _ v2: Int) -> Int { v1 * v2 }
func divide(_ v1: Int, _ v2: Int) -> Int { v1 / v2 }
func mod(_ v1: Int, _ v2: Int) -> Int { v1 % v2 }
divide(mod(sub(multiple(add(num, 3), 5), 1), 10), 2)
函数式写法
func add(_ v: Int) -> (Int) -> Int {{ $0 + v }}
func sub(_ v: Int) -> (Int) -> Int {{ $0 - v }}
func multiple(_ v: Int) -> (Int) -> Int {{ $0 * v }}
func divide(_ v: Int) -> (Int) -> Int {{ $0 / v }}
func mod(_ v: Int) -> (Int) -> Int {{ $0 % v }}
infix operator >>> : AdditionPrecedence
func >>><A, B, C>(_ f1: @escaping (A) -> B,
_ f2: @escaping (B) -> C) -> (A) -> C {{ f2(f1($0)) }}
var fn = add(3) >>> multiple(5) >>> sub(1) >>> mod(10) >>> divide(2)
fn(num)
高阶函数(Higher-Order Function)
高阶函数至少满足下列一个条件的函数:
- 接受一个或多个函数作为输入(map、filter、reduce等)
- 返回一个函数
FP中到处都是高阶函数
柯里化(Currying)
什么是柯里化?
将一个接受多参数的函数变换为一系列只接受单个参数的函数
Array、Optional
的map
方法接收的参数就是一个柯里化函数
演变示例
func add(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
add(2 + 4)
变为函数式的写法:
func add(_ v2: Int) -> (Int) -> Int {
return { v1 in
return v1 + v2
}
}
add(4)(2)
再精简:
func add(_ v2: Int) -> (Int) -> Int {{ v1 in v1 + v2 }}
add(4)(2)
再精简:
func add(_ v: Int) -> (Int) -> Int {{ $0 + v }}
add(4)(2)
柯里化:
func currying<A, B, C>(_ fn: @escaping (A, B) -> C) -> (B) -> (A) -> C {{ b in { a in fn(a, b) }}}
let curriedAdd = currying(add)
print(curriedAdd(4)(2))
func add(_ v1: Int, _ v2: Int, _ v3: Int) -> Int { v1 + v2 + v3 }
add(2, 3, 5)
变为函数式的写法:
func add(_ v3: Int) -> (Int) -> (Int) -> Int {
// v2是3
return { v2 in
// v1是2
return { v1 in
return v1 + v2 + v3
}
}
}
add(5)(3)(2)
再精简:
func add(_ v3: Int) -> (Int) -> (Int) -> Int {{ v2 in { v1 in v1 + v2 + v3 }}}
add(5)(3)(2)
柯里化:
func currying<A, B, C, D>(_ fn: @escaping (A, B, C) -> D) -> (C) -> (B) -> (A) -> D {{ c in { b in { a in fn(a, b, c) }}}}
let curriedAdd = currying(add)
print(curriedAdd(10)(20)(30))
一开始的示例就可以都保留旧的方法,然后通过柯里化来调用
func add(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
func sub(_ v1: Int, _ v2: Int) -> Int { v1 - v2 }
func multiple(_ v1: Int, _ v2: Int) -> Int { v1 * v2 }
func divide(_ v1: Int, _ v2: Int) -> Int { v1 / v2 }
func mod(_ v1: Int, _ v2: Int) -> Int { v1 % v2 }
prefix func ~<A, B, C>(_ fn: @escaping (A, B) -> C) -> (B) -> (A) -> C {{ b in { a in fn(a, b) }}}
infix operator >>> : AdditionPrecedence
func >>><A, B, C>(_ f1: @escaping (A) -> B,
_ f2: @escaping (B) -> C) -> (A) -> C {{ f2(f1($0)) }}
var num = 1
var fn = (~add)(3) >>> (~multiple)(5) >>> (~sub)(1) >>> (~mod)(10) >>> (~divide)(2)
fn(num)
函子(Functor)
像Array、Optional
这样支持map运算
的类型,称为函子(Functor)
下图充分解释了函子
适用函子(Applicative Functor)
对任意一个函子F
,如果能支持以下运算,该函子就是一个适用函子
func pure<A>(_ value: A) -> F<A>
func <*><A, B>(fn: F<(A) -> B>, value: F<A>) -> F<B>
Optional
可以成为适用函子
func pure<A>(_ value: A) -> A? { value }
infix operator <*> : AdditionPrecedence
func <*><A, B>(fn: ((A) -> B)?, value: A?) -> B? {
guard let f = fn, let v = value else { return nil }
return f(v)
}
var value: Int? = 10
var fn: ((Int) -> Int)? = { $0 * 2 }
print(fn <*> value as Any) // Optional(20)
Array
可以成为适用函子
func pure<A>(_ value: A) -> [A] { [value] }
infix operator <*> : AdditionPrecedence
func <*><A, B>(fn: [(A) -> B], value: [A]) -> [B] {
var arr: [B] = []
if fn.count == value.count {
for i in fn.startIndex..<fn.endIndex {
arr.append(fn[i](value[i]))
}
}
return arr
}
print(pure(10)) // [10]
var arr = [{ $0 * 2 }, { $0 + 10 }, { $0 - 5 }] <*> [1, 2 , 3]
print(arr) // [2, 12, -2]
单子(Monad)
对任意一共类型F
,如果能支持以下运算,那么就可以称为是一个单子
func pure<A>(_ value: A) -> F<A>
func flatMap<A, B>(_ value: F<A>, _ fn: (A) -> F<B>) -> F<B>
很显然,Array、Optional
都是单子
面向协议编程(Protocol Oriented Programming)
基本概念
面向协议编程(Protocol Oriented Programming,简称POP)
- 是Swift的一种编程范式,
Apple
于2015年WWDC
提出 - 在Swift标准库中,能见到大量
POP
的影子
同时,Swift也是一门面向对象的编程语言(Object Oriented Programming,简称OOP)
在Swift开发中,OOP
和POP
是相辅相成的,任何一方并不能取代另一方
POP
能弥补OOP
一些设计上的不足
OOP和POP
回顾OOP
OOP
的三大特性:封装、继承、多态
继承的经典使用场合:
当多个类(比如A、B、C类)具有很大共性时,可以将这些共性抽取到一个父类中(比如D类),最后A、B、C类继承D类
OOP的不足
但有些问题,使用OOP
并不能很好解决,比如如何将BVC、DVC的公共方法run
抽出来
class BVC: UIViewController {
func run() {
print("run")
}
}
class DVC: UITableViewController {
func run() {
print("run")
}
}
基于OOP
想到的一些解决方案:
1.将run方法
放到另一个对象A中,然后BVC、DVC拥有对象A属性
- 多了一些额外的依赖关系
2.将run方法
增加到UIViewController分类
中
UIViewController
会越来越臃肿,而且会影响它的其他所有子类
3.将run方法
抽取到新的父类,采用多继承(C++
支持多继承)
- 会增加程序设计的复杂度,产生菱形继承等问题,需要开发者额外解决
POP的解决方案
protocol Runnable {
func run()
}
extension Runnable {
func run() {
print("run")
}
}
class BVC: UIViewController, Runnable {}
class DVC: UITableViewController, Runnable {}
POP的注意点
- 优先考虑创建协议,而不是父类(基类)
- 优先考虑值类型(struct、enum),而不是引用类型(class)
- 巧用协议的扩展功能
- 不要为了面向协议而使用协议
POP的应用
下面我们利用协议来实现前缀效果
var string = "123fdsf434"
protocol NameSpaceWrapperProtocol {
associatedtype WrappedType
var wrappedValue: WrappedType { get set }
init(value: WrappedType)
}
struct NameSpaceWrapper<T>: NameSpaceWrapperProtocol {
var wrappedValue: T
init(value: T) {
self.wrappedValue = value
}
}
protocol NamespaceWrappable { }
extension NamespaceWrappable {
var ll: NameSpaceWrapper<Self> {
get { NameSpaceWrapper(value: self) }
set {}
}
static var ll: NameSpaceWrapper<Self>.Type {
get { NameSpaceWrapper.self }
set {}
}
}
extension NameSpaceWrapperProtocol where WrappedType: ExpressibleByStringLiteral {
var numberCount: Int {
(wrappedValue as? String)?.filter { ("0"..."9").contains($0) }.count ?? 0
}
}
extension String: NamespaceWrappable {}
extension NSString: NamespaceWrappable {}
print(string.ll.numberCount)
print((string as NSString).ll.numberCount) // 6
利用协议实现类型判断
func isArray(_ value: Any) -> Bool { value is [Any] }
print(isArray([1, 2])) // true
print(isArray(["1", 2])) // true
print(isArray(NSArray())) // true
print(isArray(NSMutableArray())) // true
protocol ArrayType {}
func isArrayType(_ type: Any.Type) -> Bool { type is ArrayType.Type }
extension Array: ArrayType {}
extension NSArray: ArrayType {}
print(isArrayType([Int].self)) // true
print(isArrayType([Any].self)) // true
print(isArrayType(NSArray.self)) // true
print(isArrayType(NSMutableArray.self)) // true
print(isArrayType(String.self)) // false
响应式编程(Reactive Programming)
基本概念
响应式编程(Reactive Programming,简称RP),是一种编程范式,于1997年提出,可以简化异步编程,提供更优雅的数据绑定
一般与函数式融合在一起,所以也会叫做:函数响应式编程(Functional Reactive Programming,简称FRP)
比较著名的、成熟的响应式框架
- ReactiveCocoa:简称RAC,有Objective-C、Swift版本
- ReactiveX:简称Rx,有众多编程语言版本,比如RxJava、RxKotlin、RxJS、RxCpp、RxPHP、RxGo、RxSwift等
RxSwift
RxSwift
(ReactiveX for Swift),ReactiveX
的Swift
版本
源码: github.com/ReactiveX/R… beeth0ven.github.io/RxSwift-Chi…
具体内容请看更详细的文章讲解