Swift基础语法(二十一)常见编程范式认识:函数式编程

1,008 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第25天,点击查看活动详情

Swift基础语法文章汇总

  1. 函数式编程的认识
  2. 高阶函数
  3. 函数柯里化
  4. 函子/适用函子
  5. 单子

1. 认识

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

主要思想: 把计算过程尽量分解成一系列可复用函数的调用。

主要特征: 函数是“第一等公民。函数与其他数据类型一样的地位,可以赋值给其他变量,也可以作为函数参数、函数返回值。

通俗的说函数式编程就是将整个的操作过程拆分成不同的函数,以函数为单位依次进行调用

常见概念:

  • Higher-Order Function(高阶函数)
  • Function Currying(函数柯里化)
  • Functor(函子)
  • Applicative Functor(适用函子)
  • Monad(单子)

2. 使用

函数式编程的简单使用

/*
 0、编程思想的认识
 两个数相加
 */
func test0() {
    /*
     1. 先传入参数v,将其作为一个函数的操作数
     2. 返回一个函数
     3. 该函数需要传入一个值,计算后返回两个值的和
     */
    let num = 1
    func add(_ v: Int) -> (Int) -> Int {
        return {
            $0 + v
        }
    }
    print("test0:",add(3)(num))
}

说明:

  • 传统上对于多个计算过程需要通过不同的函数传参进行处理,上一个函数的结果作为下一个函数的参数,这样的可读性很差,而且写法上不够优雅。
  • 使用函数式编程将计算过程进程拆分,两次调用来实现
  • 这里在调用add函数后再返回一个函数
  • 返回的函数用闭包表达式表示,操作时将传入的值$0+v
  • 因此先接收一个参数,再接收另一个参数(这就是拆分计算过程)
  • 将3+num的操作进行拆分,第一次先传入3拿到一个函数,第二次再传入一个num,对3和num进行计算

3、 高阶函数

只要满足以下任一条件就是高阶函数:

  1. 接受一个或多个函数作为输入( map、filter、reduce等)
  2. 返回一个函数

代码:

func add(_ v: Int) -> (Int) -> Int { { $0 + v } }

说明:

  • 这里返回了一个函数,其实就是高阶函数
  • 因此在函数式编程中的函数都是高阶函数

4、 柯里化(Currying)

将一个接受多参数的函数变换为一系列只接受单个参数的函数的过程就是柯里化

函数式贬称就需要将多个操作分解成不同函数,以函数为单位进行调用。因此函数式编程就需要进行柯里化

4.1 柯里化认识

图片.png

图片.png

说明:

  1. 首先传入一个函数fn
  2. 将该函数进行一定操作后进行返回。需要返回的值是一个函数(该函数为参数为B,返回值是一个(A) -> C的函数)
  3. 返回的这个函数传入的值是B类型的值,最后返回的是函数的参数为A类型的值
  4. 函数体为是调用fn(a,b)
  5. 因此这个函数将传入进来的函数fn的a+b的过程进行了拆分,分别返回两个函数,每个函数都带有一个参数。

4.2 柯里化的使用

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

图片.png

图片.png

说明:

  1. 合并函数,需要传入两个函数,返回一个函数
  2. 这里具体的操作仍然是一样的,只是调用一下
  3. 创建>>>运算符用来合并函数,因此add(3) >>> multiple(5)就是将这两个操作进行了合并,并且赋给了fn,这里的合并其实就是f2(f1($))),也就是说里面的操作和传统写法一样。
  4. 之后调用fn(num)就是将这些操作全部进行了一遍
  5. 柯里化就可以将这些运算进行拆分

5、 函子

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

代码:

/*
 3、函子:像Array、  Optional这样支持map运算的类型,称为函子( Functor)
 */
func test3() {
    
    /*map认识
     返回一个数组,其中包含将给定闭包映射到序列元素上的结果
     - Parameter transform:一个映射闭包
        1. 这个闭包接收数组元素作为它的参数
        2. 返回一个转换后的值,这个值的类型可以与参数相同,也可以不同
     - Returns:
        返回一个数组,数组元素为转换后数组的元素
    @inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
     */
    
    /*
     数组:Array<Element>
     public func map<T>(_ transform: (Element) -> T) -> Array<T>
     */
    let arr1 = [1,2,3]
    let arr2 = arr1.map { $0 * 2 }
    print("arr2:",arr2)
    /*
     可选项:Optional<Wrapped>
     public func map<U>(_ transform: (Wrapped) -> U) -> Optional<U>
     */
    let a: Int? = 1
    let b: Int? = a.map { $0 * 3 }
    print("b:",b!)
}

说明:

  1. 取出Type内部元素进行处理
  2. 处理后再包装到Type类型中返回

6、 适用函子

条件:

func pure<A>(_ value: A) -> F<A>
func <*><A, B>(fn: F<(A) -> B>, value: F<A>) -> F<B>
  • 第一个函数条件

    • 随便给一个类型,都能用函子包装,并且泛型是这个类型
    • 传入类型为A的值
    • 返回一个对A类型的操作
  • 第二个函数条件

    • 传入两个参数,第一个参数是F泛型一个函数,并且函数是通过A类型得到B类型,第二个参数是F泛型A。最后得到F泛型B
  • 操作是包装的,参数是包装的,返回也是包装的,就是适用函子

适用函子

代码:

/*
 4、适用函子
 */
//Optional的适用函子条件
func pure<A>(_ value: A) -> A? { value }//易理解
infix operator <*> : AdditionPrecedence
//传入可选项函数(A) -> B ,传入A? ,返回B?
//将A传入函数fn中,拿到的仍然是个可选项
func <*><A, B>(fn: ((A) -> B)?, value: A?) -> B? {
    guard let f = fn, let v = value else { return nil }
    return f(v)
}

//Array的适用函子条件
func pure<A>(_ value: A) -> [A] { [value] }//易理解
//传入数组元素为函数,传入数组元素为A,拿到数组元素为B
//取出fn和value的元素分别进行调用。返回结果放到一个数组中
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
}

//调用
func test4() {
    var value: Int? = 10
    var fn: ((Int) -> Int)? = { $0 * 2}
    // Optional(20)
    print(fn <*> value as Any)
    
    
    // [10]
    print(pure(10))
    var arr = [{ $0 * 2}, { $0 + 10 }, { $0 - 5 }] <*> [1, 2, 3]
    // [2, 12, -2]
    print(arr)
}

说明:

  • Optional的适用函子
    • 第一函数条件:传入一个数值,之后返回可选项
    • 第二个函数条件
      • 第一个参数函数fn是一个包装函数的可选项,第二个参数是是可选项A,返回可选项B
      • 值绑定模式中,f为解包后的函数,v为解包后的值
      • 他们进行调用,调用后返回了一个值,是可选项
  • Array的适用函子
    • 存放函数的数组,存放A的数组,之后拿到存放B的数组

7、 单子

代码:

func pure<A>(_ value: A) -> F<A>
func flatMap<A, B>(_ value: F<A>, _ fn: (A) -> F<B>) -> F<B>

说明:

  • 函数一:传入一个值,返回改值的包装
  • 函数二:传入一个值A的包装,一个函数,函数是通过A得到B的包装,通过函数的计算最后返回B的包装

很显然, Array、 Optional也都是单子