zoukankan      html  css  js  c++  java
  • 编程范式 —— 函数式编程入门

    该系列会有 3 篇文章,分别介绍什么是函数式编程、剖析函数式编程库、以及函数式编程在 React 中的应用,欢迎关注我的 blog

    命令式编程和声明式编程

    拿泡茶这个事例进行区分命令式编程和声明式编程

    • 命令式编程

    1.烧开水(为第一人称)
    2.拿个茶杯
    3.放茶叶
    4.冲水

    • 声明式编程

    1.给我泡杯茶(为第二人称)

    举个 demo

    // 命令式编程
    const convert = function(arr) {
      const result = []
      for (let i = 0; i < arr.length; i++) {
        result[i] = arr[i].toLowerCase()
      }
      return result
    }
    
    // 声明式编程
    const convert = function(arr) {
      return arr.map(r => r.toLowerCase())
    }
    

    什么是函数式编程

    函数式编程是声明式编程的范式。在函数式编程中数据在由纯函数组成的管道中传递。

    函数式编程可以用简单如交换律、结合律、分配律的数学之法来帮我们简化代码的实现。

    它具有如下一些特性:

    • 纯粹性: 纯函数不改变除当前作用域以外的值;
    // 反面示例
    let a = 0
    const add = (b) => a = a + b // 两次 add(1) 结果不一致
    
    // 正确示例
    const add = (a, b) => a + b
    
    • 数据不可变性: Immutable
    // 反面示例
    const arr = [1, 2]
    const arrAdd = (value) => {
      arr.push(value)
      return arr
    }
    
    arrAdd(3) // [1, 2, 3]
    arrAdd(3) // [1, 2, 3, 3]
    
    // 正面示例
    const arr = [1, 2]
    const arrAdd = (value) => {
      return arr.concat(value)
    }
    
    arrAdd(3) // [1, 2, 3]
    arrAdd(3) // [1, 2, 3]
    

    在后记 1 中对数组字符串方法是否对原值有影响作了整理

    • 函数柯里化: 将多个入参的函数转化为一个入参的函数;
    const add = a => b => c => a + b + c
    add(1)(2)(3)
    
    • 偏函数: 将多个入参的函数转化成两部分;
    const add = a => (b, c) => a + b + c
    add(1)(2, 3)
    
    • 可组合: 函数之间能组合使用
    const add = (x) => x + x
    const mult = (x) => x * x
    
    const addAndMult = (x) => add(mult(x))
    

    柯里化(curry)

    如下是一个加法函数:

    var add = (a, b, c) => a + b + c
    
    add(1, 2, 3) // 6
    

    假如有这样一个 curry 函数, 用其包装 add 函数后返回一个新的函数 curryAdd, 我们可以将参数 a、b 进行分开传递进行调用。

    var curryAdd = curry(add)
    
    // 以下输出结果都相同
    curryAdd(1, 2, 3) // 6
    curryAdd(1, 2)(3) // 6
    curryAdd(1)(2)(3) // 6
    curryAdd(1)(2, 3) // 6
    

    动手实现一个 curry 函数

    核心思路: 若传进去的参数个数未达到 curryAdd 的个数,则将参数缓存在闭包变量 lists 中:

    function curry(fn, ...args) {
      const length = fn.length
      let lists = args || []
    
      let listLen
      return function (..._args) {
        lists = [...lists, ..._args]
        listLen = lists.length
    
        if (listLen < length) {
          const that = lists
          lists = []
          return curry(fn, ...that)
        } else if (listLen === length) {
          const that = lists
          lists = []
          return fn.apply(this, that)
        }
      }
    }
    

    代码组合(compose)

    现在有 toUpperCasereversehead 三个函数, 分别如下:

    var toUpperCase = (str) => str.toUpperCase()
    var reverse = (arr) => arr.reverse()
    var head = (arr) => arr[0]
    

    接着使用它们实现将数组末位元素大写化输出, 可以这样做:

    var reverseHeadUpperCase = (arr) => toUpperCase(head(reverse(arr)))
    
    reverseHeadUpperCase(['apple', 'banana', 'peach']) // "PEACH"
    

    此时在构建 reverseHeadUpperCase 函数的时候, 必须手动声明传入参数 arr, 是否能提供一个 compose 函数让使用者更加友好的使用呢? 类似如下形式:

    var reverseHeadUpperCase = compose(toUpperCase, head, reverse)
    
    reverseHeadUpperCase(['apple', 'banana', 'peach']) // "PEACH"
    

    此外 compose 函数符合结合律, 我们可以这样子使用:

    compose(compose(toUpperCase, head), reverse)
    compose(toUpperCase, compose(head, reverse))
    

    以上两种写法与 compose(toUpperCase, head, reverse) 的效果完全相同, 都是依次从右到左执行传参中的函数。

    此外 composemap 一起使用时也有相关的结合律, 以下两种写法效果相等

    compose(map(f), map(g))
    map(compose(f, g))
    

    动手实现一个 compose 函数

    代码精华集中在一行之内, 其为众多开源库(比如 Redux) 所采用。

    var compose = (...args) => (initValue) => args.reduceRight((a, c) => c(a), initValue)
    

    范畴论

    范畴论是数学中的一个分支。可以将范畴理解为一个容器, 把原来对值的操作,现转为对容器的操作。如下图:

    学习函数式编程就是学习各种函子的过程。

    函数式编程中, 函子(Functor) 是实现了 map 函数的容器, 下文中将函子视为范畴,模型可表示如下:

    class Functor {
      constructor(value) {
        this.value = value
      }
    
      map(fn) {
        return new Functor(fn(this.value))
      }
    }
    

    但是在函数式编程中, 要避免使用 new 这种面向对象的编程方式, 取而代之对外暴露了一个 of 的接口, 也称为 pointed functor

    Functor.of = value => new Functor(value)
    

    Maybe 函子

    Maybe 函子是为了解决 this.value 为 null 的情形, 用法如下:

    Maybe.of(null).map(r => r.toUpperCase()) // null
    Maybe.of('m').map(r => r.toUpperCase())  // Maybe {value: "M"}
    

    实现代码如下:

    class Maybe {
      constructor(value) {
        this.value = value
      }
    
      map(fn) {
        return this.value ? new Maybe(fn(this.value)) : null
      }
    }
    
    Maybe.of = value => new Maybe(value)
    

    Either 函子

    Either 函子 是为了对应 if...else... 的语法, 即非左即右。因此可以将之拆分为 LeftRight 两个函子, 它们的用法如下:

    Left.of(1).map(r => r + 1)  // Left {value: 1}
    
    Right.of(1).map(r => r + 1) // Right {value: 2}
    

    Left 函子实现代码如下:

    class Left {
      constructor(value) {
        this.value = value
      }
    
      map(fn) {
        return this
      }
    }
    
    Left.of = value => new Left(value)
    

    Right 函子实现代码如下(其实就是上面的 Functor):

    class Right {
      constructor(value) {
        this.value = value
      }
    
      map(fn) {
        return new Right(fn(this.value))
      }
    }
    
    Right.of = value => new Right(value)
    

    具体 Either 函数只是对调用 Left 函子Right 函子 作一层筛选, 其接收 fg 两个函数以及一个函子(Left or Right)

    var Either = function(f, g, functor) {
      switch(functor.constructor) {
        case 'Left':
          return f(functor.value)
        case 'Right':
          return g(functor.value)
        default:
          return f(functor.value)
      }
    }
    

    使用 demo:

    Either((v) => console.log('left', v), (v) => console.log('def', v), left)   // left 1
    Either((v) => console.log('rigth', v), (v) => console.log('def', v), rigth) // rigth 2
    

    Monad 函子

    函子会发生嵌套, 比如下面这样:

    Functor.of(Functor.of(1)) // Functor { value: Functor { value: 1 } }
    

    Monad 函子 对外暴露了 joinflatmap 接口, 调用者从而可以扁平化嵌套的函子。

    class Monad {
      constructor(value) {
        this.value = value
      }
    
      map(fn) {
        return new Monad(fn(this.value))
      }
    
      join() {
        return this.value
      }
    
      flatmap(fn) {
        return this.map(fn).join()
      }
    }
    
    Monad.of = value => new Monad(value)
    

    使用方法:

    // join
    Monad.of(Monad.of(1).join()) // Monad { value: 1 }
    Monad.of(Monad.of(1)).join() // Monad { value: 1 }
    
    // flatmap
    Monad.of(1).flatmap(r => r + 1)  // 2
    

    Monad 函子可以运用在 I/O 这种不纯的操作上将之变为纯函数的操作,目前比较懵懂,日后补充。

    后记 1: 数组字符串方法小结(是否对原值有影响)

    不会对原数组有影响的方法

    slice
    var test = [1, 2, 3]
    var result = test.slice(0, 1)
    
    console.log(test)   // [1, 2, 3]
    console.log(result) // [1]
    
    concat
    var test = [1, 2, 3]
    var result = test.concat(4)
    
    console.log(test)   // [1, 2, 3]
    console.log(result) // [1, 2, 3, 4]
    

    对原数组有影响的方法

    splice(这个需要特别记一下)
    var test = [1, 2, 3]
    var result = test.splice(0, 1)
    
    console.log(test)   // [2, 3]
    console.log(result) // [1]
    
    sort
    var arr = [2, 1, 3, 4]
    arr.sort((r1, r2) => (r1 - r2))
    
    console.log(arr) // [1, 2, 3, 4]
    
    reverse
    var test = [1, 2, 3]
    var result = test.reverse()
    
    console.log(test)   // [3, 2, 1]
    console.log(result) // [3, 2, 1]
    
    push/pop/unshift/shift
    var test = [1, 2, 3]
    var result = test.push(4)
    
    console.log(test)   // [1, 2, 3, 4]
    console.log(result) // 4
    

    不会对原字符串造成影响的方法

    substr/substring/slice
    // substr
    var test = 'abc'
    var result = test.substr(0, 1)
    
    console.log(test)   // 'abc'
    console.log(result) // a
    
    // substring
    var test = 'abc'
    var result = test.substring(0, 1)
    
    console.log(test)   // 'abc'
    console.log(result) // a
    
    // slice
    var test = 'abc'
    var result = test.slice(0, 1)
    
    console.log(test)   // 'abc'
    console.log(result) // a
    

    参考

  • 相关阅读:
    is 和 == 区别@编码
    python字典
    python的简介
    初始python3
    初始python2
    初始python1
    Codeforces Round #596 (Div. 2, based on Technocup 2020 Elimination Round 2) B2. TV Subscriptions (Hard Version)
    Codeforces Round #596 (Div. 2, based on Technocup 2020 Elimination Round 2) A. Forgetting Things
    2019ICPC区域赛(银川)总结
    Educational Codeforces Round 74 (Rated for Div. 2) D. AB-string
  • 原文地址:https://www.cnblogs.com/MuYunyun/p/10352716.html
Copyright © 2011-2022 走看看