Swift 进阶(十六)函数式编程、面向协议编程、响应式编程

函数式编程(Funtional Programming)

基本概念

函数式编程(Funtional Programming,简称FP)是一种编程范式,也就是如何编写程序的方法论

  • 主要思想:把计算过程尽量分解成一系列可复用函数的调用
  • 主要特征:”函数的第一等公民“,函数与其他数据类型一样的地位,可以赋值给其他变量,也可以作为函数参数、函数返回值

函数式编程最早出现在LISP语言,绝大部分的现代编程语言也对函数式编程做了不同程度的支持,比如Haskell、JavaScript、Swift、Python、Kotlin、Scala

函数式编程中几个常用概念

  • Higher-Order Function、Function Currying
  • Functor、Applicative Functor、Monad

参考资料:
http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
https://mokacoding.com/blog/functor-applicative-monads-in-pictures/

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]

mapflatMap、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-----

OptionalmapflatMap

会先将可选类型解包,处理完会再进行包装返回出去

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)

什么是柯里化?

将一个接受多参数的函数变换为一系列只接受单个参数的函数

-w633

Array、Optionalmap方法接收的参数就是一个柯里化函数

-w619

演变示例

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)

-w619
-w601

下图充分解释了函子

-w910

适用函子(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开发中,OOPPOP是相辅相成的,任何一方并不能取代另一方

POP能弥补OOP一些设计上的不足

OOP和POP

回顾OOP

OOP的三大特性:封装、继承、多态

继承的经典使用场合:

当多个类(比如A、B、C类)具有很大共性时,可以将这些共性抽取到一个父类中(比如D类),最后A、B、C类继承D类

-w514

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 {}

-w447

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),ReactiveXSwift版本

源码:
https://github.com/ReactiveX/RxSwift
https://beeth0ven.github.io/RxSwift-Chinese-Documentation/

具体内容请看更详细的文章讲解

posted on 2021-03-31 02:38  FunkyRay  阅读(169)  评论(0编辑  收藏  举报