Swift函数式编程与面向协议编程

函数式编程(FP)

/// 把接受两个参数的函数, 变为接受一个参数
let num = 1
func add(_ v: Int) -> (Int) -> Int{
  reutrn {
    $0 + v
  }
}
let fn = add(3)
fn(num)
//简写: add(3)(num) //函数式编程的特征, 先接受一个参数, 再接受一个参数
print(fn(5), fn(100), fn(1000))

/// 函数合成
let fn1 = add(3)
let fn2 = multiple(5)
let fn3 = sub(1)
let fn4 = mod(10)
let fn5 = divide(2)

// 假设要实现以下功能: [(num + 3) * 5 - 1] % 10 / 2
//print(fn5(fn4(fn3(fn2(fn1(num))))))
func composite(_ f1: @escaping (Int) -> Int, f2: @escaping (Int) -> Int) -> (Int) -> Int{
  return {
    f2(f1($0))
  }
}
let fn = composite(add(3), multiple(5))
print(fn(num))//20

//对上面👆优化:
infix operator >>> : AdditionPrecedence //定义>>> 使它跟加法差不多
/*
func >>>(_ f1: @escaping (Int) -> Int, f2: @escaping (Int) -> Int) -> (Int) -> Int{
  return {
    f2(f1($0))
  }
}
*/
func >>><A, B, C>(_ f1: @escaping (A) -> B, f2: @escaping (B) -> C) -> (A) -> C {
  return {
    f2(f1($0))
  }
}
let fn = add(3) >>> multiple(5) >>> sub(1) >>> mod(10) >>> divide(2)
print(fn(num))//20

一、函数式编程(FP)-高阶函数

高阶函数至少满足下面的一个条件的函数:
接受一个或多个函数作为输入(map、filter、reduce等)
返回一个函数

二、函数式编程(FP) - 柯里化(Currying)

什么是柯里化? 将一个接受多参数的函数变换为一系列只接受单个参数的函数.
Array、Optional的map函数就是柯里化函数.

给定任何一个函数都能柯里化, 比如下面👇
func add1(_ v1: Int, _ v2: Int) -> Int {v1 + v2}
add1(10, 20)
//柯里化后的效果: add3(20)(10)
func add3(_ v: Int) -> (Int) -> Int { { $0 + v } }

func add2(_ v1: Int, _ v2: Int, _ v3: Int) -> Int { v1 + v2 + v3 }
add2(10, 20, 30)
//柯里化后的效果: add4(30)(20)(10) 
func add4(_ v3: Int) -> (Int) -> (Int) -> Int { 
  return { v2 in //v2 == 20
      return { v1 in // v1 == 10
        return v1 + v2 + v3
      }
  } 
}


/// 柯里化最终版本: 传一个两个参数的函数过来, 自动柯里化
func currying<A, B, C>(_ fn: @escaping (A, B) -> C) -> (B) ->(A) -> C{
  return { b in
    return { a in
      return fn(a, b)
    }
  }
}
curring(add1)(20)(10)//使用效果
//对上面👆简化:
prefix func ~<A, B, C>(_ fn: @escaping(A, B)-> C) ->(B) ->(A) -> C {
  {b in { a in fn(a, b) } }
}
print((~sub)(20)(10))


infix operator >>> : AdditionPrecedence
func >>> <A, B, C>(_ f1: @escaping (A) -> B,
                  _ f2: @escaping(B) -> C)
    -> (A) -> C { { f2(f1($0)) } }
let fn = (~add)(3) >>> (~multiple)(5) >>> (~sub)(1) >>> (~mod)(10) >>> (~divide)(2)
print(fn(1))

/// 柯里化最终版本: 传一个三个参数的函数过来, 实现自动柯里化
prefix func ~<A, B, C, D>(_ fn: @escaping(A, B, C)-> D) ->(C) ->(B) ->(A) -> D {
  {c in {b in { a in fn(a, b, c) } } }
}
print((~add2)(30)(20)(10))

三、函数式编程-函子(Functor)

像Array、Optional这样支持map运算的类型, 称为函子(Functor).

// Array<Element>
public func map<T>(_ transform: (Element) ->T) -> Array<T>
// Optional<Wrapped>
public func map<U>(_ transform: (Wrapped) -> U) -> Optional<U>

//怎么样的Type才能称为函子(Functor)?
func map<T>(_ fn: (Inner) -> T) > Type<T>//支持这个运算格式的就是函子

适用函子

四、函数式编程-适用函子(Applicative Functor)

对任意一个函子F, 如果能支持一下运算, 该函子就是一个适用函子
func pure<A>(_ value: A) -> F<A>
func <*><A, B>(fn: F<(A)->B>, value: F<A>) -> F<B>

五、函数式编程-单子(Monad)

对任意一个类型F, 如果能支持一下运算, 那么就可以称为是一个单子(Monad)
func pure<A>(_ value: A) -> F<A>
func flatMap<A, B>(_ value: F<A>, _ fn:(A) -> F<B>) ->F<B>

很显然Array和Optional是单子

面向协议编程(POP)

面向协议编程(Protocol Oriented Programming, 简称POP)
  是Swift的一种编程范式, Apple于2015年WWDC提出
  在Swift的标准库中, 能见到大量POP的影子

同时, Swift也是一门面向对象的编程语言(Object Oriented Programming, 简称OOP)
    在Swift开发中, OOP和POP相辅相成的

OOP的三大特性: 封装、继承、多态
继承的经典使用场景:
    当多个类(比如A、B、C类)具有很多共性时, 可以将这些共性抽取到一个父类中(比如D类),最后A、B、C都继承D类
    
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 { //Swift支持扩展协议的具体实现(类似Java的接口)
  func run() {
    print("run")
  }
}
class BVC: UIViewController, RunNable {}
class DVC: UITableViewController, Runnable {}

//POP的注意点
优先考虑创建协议, 而不是父类(基类)
优先考虑值类型( struct、enum), 而不是引用类型(class)
巧用协议的扩展功能
不要为了面向协议而使用协议

一、给类拓展功能

var str = "123rrr"
/// 需求: 查找字符串中有几个数字
//方法一: 通过扩展
extension String{
    var mj_numberCount: Int {//计算属性
    var count = 0
    for c in self where ("0"..."9").contains(c){
      count += 1
    }
    return count
  }
}
print(str.mj_numberCount)

/// 对上面👆进行优化升级, 效果: print(str.mj.numberCount)
var str = "123rrr"
struct MJ {
    var string: String
    init(_ string: String){//持有外部传入的参数
        self.string = string
    }

    var numberCount: Int {//扩展功能方法
        var count = 0
        for c in string where ("0"..."9").contains(c){
            count += 1
        }
        return count
    }
}

extension String{
    var mj: MJ { return MJ(self) }
}
print("123ooo999".mj.numberCount)

/// 对上面👆进行优化升级, 不能只支持String类型, 让它更通用
struct MJ<Base> {//使用泛型增加通用性
    var base: Base
    init(_ base: Base){//持有外部传入的参数
        self.base = base
    }
}
extension String{
    var mj: MJ<String> { return MJ(self) }
}

class Person {}
extension Person{
    var mj: MJ<Person> { return MJ(self) }
}
//扩展的是对象方法
extension MJ where Base == String { //此处是计算属性, 本质是一个方法
    var numberCount: Int {//扩展功能方法
        var count = 0
        for c in base where ("0"..."9").contains(c){
            count += 1
        }
        return count
    }
}

extension MJ where Base: Person {
    func run() {//扩展功能方法
        print("跑起来了")
    }
}

print("12kkk999".mj.numberCount)
Person().mj.run()

/// 对上面👆进行优化升级, 支持拓展类方法. 效果String.mj.test()
//sp1.前缀类型
struct MJ<Base> {//使用泛型增加通用性
    var base: Base
    init(_ base: Base){//持有外部传入的参数
        self.base = base
    }
}
//sp2.让想扩充方法的类型, 扩充前缀属性
extension String{
    var mj: MJ<String> { MJ(self) }//返回实例属性
    static var mj: MJ<String>.Type { return MJ<String>.self } // static属性用类是可以访问出来的, 返回类型属性
    //注意: xx类.self 的类型是 xx类.Type. 比如上面 MJ.self 的类型是 MJ.Type
}
//sp3.给前缀扩展方法
extension MJ where Base == String {
    var numberCount: Int {//扩展实例方法
        var count = 0
        for c in base where ("0"..."9").contains(c){
            count += 1
        }
        return count
    }
    
    static func test(){//扩展类方法
        print("执行了类方法")
    }
}
String.mj.test()

/// 对上面👆sp2进行优化升级, 利用协议protocol扩展前缀属性
//sp1.前缀类型
struct MJ<Base> {//使用泛型增加通用性
    var base: Base
    init(_ base: Base){//持有外部传入的参数
        self.base = base
    }
}

//sp2.利用协议protocol扩展前缀属性
protocol MJCompatible {} //协议里面只能声明一些东西,想给协议扩展一些东西的话要用extension
extension MJCompatible{//扩展协议
    var mj: MJ<Self> { MJ(self) }//返回实例属性
    static var mj: MJ<Self>.Type { MJ<Self>.self } // static属性用类是可以访问出来的, 返回类型属性
    //注意: xx类.self 的类型是 xx类.Type. 比如上面 MJ.self 的类型是 MJ.Type
}
//以后谁想给前缀扩展这个功能, 就让让它遵守这个协议
extension String: MJCompatible{} //让String拥有mj前缀属性

//sp3.给String.mj、String().mj前缀扩展功能
extension MJ where Base == String {
    var numberCount: Int {//扩展实例方法
        var count = 0
        for c in base where ("0"..."9").contains(c){
            count += 1
        }
        return count
    }
    
    static func test(){//扩展类方法
        print("执行了类方法")
    }
}
String.mj.test()

/// 对上面👆进行优化升级, 支持扩展mutating功能 ✅
//sp1.前缀类型
struct MJ<Base> {//使用泛型增加通用性
    var base: Base
    init(_ base: Base){//持有外部传入的参数
        self.base = base
    }
}
//sp2.利用协议protocol扩展前缀属性
protocol MJCompatible {} //协议里面只能声明一些东西,想给协议扩展一些东西的话要用extension
extension MJCompatible{//扩展协议
    var mj: MJ<Self> {//不要返回只读的计算属性, 为了以后扩展mutating, 换成普通计算属性
        set {} //为了mutating语法能够通过
        get { MJ(self) }
    }
    static var mj: MJ<Self>.Type {
        set {}
        get { MJ<Self>.self }
    } // static属性用类是可以访问出来的, 返回类型属性
    //注意: xx类.self 的类型是 xx类.Type. 比如上面 MJ.self 的类型是 MJ.Type
}
extension String: MJCompatible{} //让String遵守这个协议
//sp3.给String.mj、String().mj前缀扩展功能
extension MJ where Base == String {
    var numberCount: Int {//扩展实例方法
        var count = 0
        for c in base where ("0"..."9").contains(c){
            count += 1
        }
        return count
    }
    
    mutating func run(){//mutating作用是让MJ(结构体)实例是可修改的
        //切记常量是不可修改的, 不要用常量调用mutating方法
        print("执行了mutating方法")
    }
    
    static func test(){//扩展类方法
        print("执行了类方法")
    }
}
var str = "123321"
str.mj.run()

//注意: 父类扩展了功能, 子类也会拥有这些功能.

二、给NSString、String、NSMutableString同时扩展方法

//NSString、String、NSMutableString共同点: 因为它们都遵循ExpressibleByStringLiteral这个协议,才能用字符串字面量进行初始化.
struct MJ<Base> {
    var base: Base
    init(_ base: Base){
        self.base = base
    }
}
protocol MJCompatible {}
extension MJCompatible{
    var mj: MJ<Self> {
        set {}
        get { MJ(self) }
    }
    static var mj: MJ<Self>.Type {
        set {}
        get { MJ<Self>.self }
    }
}
extension String: MJCompatible{}
extension NSString: MJCompatible{}//NSString遵守了和这个协议, 它的子类NSMutableString也就遵守了这个协议

//代表我这个泛型遵守ExpressibleByStringLiteral这个协议的, 都可以用numberCount方法
extension MJ where Base: ExpressibleByStringLiteral {//Base:后面能放协议、结构体、类型、枚举
    var numberCount: Int {
//        // 只要是能通过字符串字面量初始化的, 都是能转成Swift的String的
//        // 能够通过字符串字面量初始化的只有NSString、String、NSMutableString
//        var string = base as! String
        var count = 0
        for c in (base as! String) where ("0"..."9").contains(c){
            count += 1
        }
        return count
    }
    
    mutating func run(){//mutating作用是让MJ(结构体)实例是可修改的
        //切记常量是不可修改的, 不要用常量调用mutating方法
        print("执行了mutating方法")
    }
    
    static func test(){//扩展类方法
        print("执行了类方法")
    }
}

var str1 = "123321"
var str2: NSString = "123xxx"
var str3: NSMutableString = "123xxx"
print(str1.mj.numberCount)
print(str2.mj.numberCount)
print(str3.mj.numberCount)

三、利用协议实现类型判断

需求1: 判断传入的实例对象是否为数组.
func isArray(_ value: Any)->Bool{
//        return value is Array<Any>
    return value is [Any] //对上面👆的简写: Any表示数组里可以装入任何东西
}

print(isArray([1, 2]))
print(isArray(["1", 2]))
print(isArray(NSArray())) //OC的类型都是能直接桥接Swift里对应的类型, 例如: NSArray() as Array
print(isArray(NSMutableArray()))
print(isArray("23123"))

需求2: 判断传入的类型是否是数组类型
// 一般判断某个类型是否是数组类型,这么判断:
let type = NSArray.self
print(type is NSArray.Type
      
//实例中, 判断传入的类型是否是数组类型
//sp1.定义一个协议,让所有数组类型都遵守这个协议
protocol ArrayType {}
extension Array: ArrayType{}
extension NSArray: ArrayType{}
//sp2.判断类型是否是协议的type
func isArrayType(_ type: Any.Type) ->Bool {
//        type is [Any].Type//这样写无法正确判断出数组类型, 因为[Any].Type和[Int].type等不是一个东西
    //解决方法: 使用协议来完成这个判断
    type is ArrayType.Type//协议最终就是一个具体类型
}
//sp3.实战中使用
print(isArrayType([Int].self))
print(isArrayType([Any].self))
print(isArrayType(NSArray.self))
print(isArrayType(NSMutableArray.self))
print(isArrayType(String.self))

//不管是枚举、协议、类、结构体, 都有.Type
//定义ttt这个变量, 表示遵守ArrayType协议的类, 都可以写过来
var ttt: ArrayType.Type //xxx.Type存放的是xxx.self
ttt = Array<Int>.self
ttt = NSArray.self
ttt = NSMutableArray.self
ttt = String.self //报错

小补充:

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

推荐阅读更多精彩内容