• 函数式编程(Functional Programming)


    JavaScript函数式编程(纯函数、柯里化以及组合函数)

     

    JavaScript函数式编程(纯函数、柯里化以及组合函数)

    前言

    函数式编程(Functional Programming),又称为泛函编程,是一种编程范式。早在很久以前就提出了函数式编程这个概念了,而后面一直长期被面向对象编程所统治着,最近几年函数式编程又回到了大家的视野中,JavaScript是一门以函数为第一公民的语言,必定是支持这一种编程范式的,下面就来谈谈JavaScript函数式编程中的核心概念纯函数、柯里化以及组合函数。

    1.纯函数

    1.1.纯函数的概念

    对于纯函数的定义,维基百科中是这样描述的:在程序设计中,若函数符合以下条件,那么这个函数被称之为纯函数。

    • 此函数在相同的输入值时,需产生相同的输出
    • 函数的输入和输出值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关
    • 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等;

    对以上描述总结就是:

    • 对于相同的输入,永远会得到相同的输出;
    • 在函数的执行过程中,没有任何可观察的副作用;
    • 同时也不依赖外部环境的状态;

    1.2.副作用

    上面提到了一个词叫“副作用”,那么什么是副作用呢?

    • 通常我们所说的副作用大多数是指药会产生的副作用;
    • 而在计算机科学中,副作用指在执行一个函数时,除了得到函数的返回值以外,还在函数调用时产生了附加的影响,比如修改了全局变量的状态,修改了传入的参数或得到了其它的输出内容等;

    1.3.纯函数案例

    • 编写一个求和的函数sum,只要我们输入了固定的值,sum函数就会给我们返回固定的结果,且不会产生任何副作用。

      function sum(a, b) {
        return a + b
      }
      
      const res = sum(10, 20)
      console.log(res) // 30
      
    • 以下的sum函数虽然对于固定的输入也会返回固定的输出,但是函数内部修改了全局变量message,就认定为产生了副作用,不属于纯函数。

      let message = 'hello'
      function sum(a, b) {
        message = 'hi'
        return a + b
      }
      
    • 在JavaScript中也提供了许多的内置方法,有些是纯函数,有些则不是。像操作数组的两个方法slice和splice。

      • slice方法就是一个纯函数,因为对于同一个数组固定的输入可以得到固定的输出,且没有任何副作用;

        const nums = [1, 2, 3, 4, 5]
        const newNums = nums.slice(1, 3)
        console.log(newNums) // [2, 3]
        console.log(nums) // [ 1, 2, 3, 4, 5 ]
        
      • splice方法不是一个纯函数,因为它改变了原数组nums;

        const nums = [1, 2, 3, 4, 5]
        const newNums = nums.splice(1, 3)
        console.log(newNums) // [ 2, 3, 4 ]
        console.log(nums) // [ 1, 5 ]
        

    2.柯里化

    2.1.柯里化的概念

    对于柯里化的定义,维基百科中是这样解释的:

    • 柯里化是指把接收多个参数的函数,变成接收一个单一参数(最初函数的第一个参数)的函数,并且返回接收余下的参数,而且返回结果的新函数的技术;
    • 柯里化声称“如果你固定某些参数,你将得到接受余下参数的一个函数”

    总结:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数的过程就称之为柯里化。

    2.2.函数柯里化的过程

    编写一个普通的三值求和函数:

    function sum(x, y, z) {
      return x + y + z
    }
    
    const res = sum(10, 20, 30)
    console.log(res) // 60
    

    将以上求和函数柯里化得:

    • 将传入的三个参数进行拆解,依次返回一个函数,并传入一个参数;
    • 在保证同样功能的同时,其调用方式却发生了变化;
    • 注意:在拆解参数时,不一定非要将参数拆成一个个的,也可以拆成2+1或1+2;
    function sum(x) {
      return function(y) {
        return function(z) {
          return x + y + z
        }
      }
    }
    
    const res = sum(10)(20)(30)
    console.log(res)
    

    使用ES6箭头函数简写为:

    const sum = x => y => z => x + y + z
    

    2.3.函数柯里化的特点及应用

    • 让函数的职责更加单一。柯里化可以实现让一个函数处理的问题尽可能的单一,而不是将一大堆逻辑交给一个函数来处理。

      • 将上面的三值求和函数增加一个需求,在计算结果之前给每个值加上2,先看看不使用柯里化的实现效果:

        function sum(x, y, z) {
          x = x + 2
          y = y + 2
          z = z + 2
          return x + y + z
        }
        
      • 柯里化的实现效果:

        function sum(x) {
          x = x + 2
          return function(y) {
            y = y + 2
            return function(z) {
              z = z + 2
              return x + y + z
            }
          }
        }
        
      • 很明显函数柯里化后,让我们对每个参数的处理更加单一

    • 提高函数参数逻辑复用。同样使用上面的求和函数,增加另一个需求,固定第一个参数的值为10,直接看柯里化的实现效果吧,后续函数调用时第一个参数值都为10的话,就可以直接调用sum10函数了。

      function sum(x) {
        return function(y) {
          return function(z) {
            return x + y + z
          }
        }
      }
      
      const sum10 = sum(10) // 指定第一个参数值为10的函数
      const res = sum10(20)(30)
      console.log(res) // 60
      

    2.4.自动柯里化函数的实现

    function autoCurrying(fn) {
      // 1.拿到当前需要柯里化函数的参数个数
      const fnLen = fn.length
    
      // 2.定义一个柯里化之后的函数
      function curried_1(...args1) {
        // 2.1拿到当前传入参数的个数
        const argsLen = args1.length
    
        // 2.1.将当前传入参数个数和fn需要的参数个数进行比较
        if (argsLen >= fnLen) {
          // 如果当前传入的参数个数已经大于等于fn需要的参数个数
          // 直接执行fn,并在执行时绑定this,并将对应的参数数组传入
          return fn.apply(this, args1)
        } else {
          // 如果传入的参数不够,说明需要继续返回函数来接收参数
          function curried_2(...args2) {
            // 将参数进行合并,递归调用curried_1,直到参数达到fn需要的参数个数
            return curried_1.apply(this, [...args1, ...args2])
          }
    
          // 返回继续接收参数函数
          return curried_2
        }
      }
    
      // 3.将柯里化的函数返回
      return curried_1
    }
    

    测试:

    function sum(x, y, z) {
      return x + y + z
    }
    
    const curryingSum = autoCurrying(sum)
    
    const res1 = curryingSum(10)(20)(30)
    const res2 = curryingSum(10, 20)(30)
    const res3 = curryingSum(10)(20, 30)
    const res4 = curryingSum(10, 20, 30)
    console.log(res1) // 60
    console.log(res2) // 60
    console.log(res3) // 60
    console.log(res4) // 60
    

    3.组合函数

    组合函数(Compose Function)是在JavaScript开发过程中一种对函数的使用技巧、模式。对某一个数据进行函数调用,执行两个函数,这两个函数需要依次执行,所以需要将这两个函数组合起来,自动依次调用,而这个过程就叫做函数的组合,组合形成的函数就叫做组合函数。

    需求:对一个数字先进行乘法运算,再进行平方运算。

    • 一般情况下,需要先定义两个函数,然后再对其依次调用:

      function double(num) {
        return num * 2
      }
      function square(num) {
        return num ** 2
      }
      
      const duobleResult = double(10)
      const squareResult = square(duobleResult)
      console.log(squareResult) // 400
      
    • 实现一个组合函数,将duoble和square两个函数组合起来:

      function composeFn(fn1, fn2) {
        return function(num) {
          return fn2(fn1(num))
        }
      }
      
      const execFn = composeFn(double, square)
      const res = execFn(10)
      console.log(res) // 400
      

    实现一个自动组合函数的函数:

    function autoComposeFn(...fns) {
      // 1.拿到需要组合的函数个数
      const fnsLen = fns.length
    
      // 2.对传入的函数进行边界判断,所有参数必须为函数
      for (let i = 0; i < fnsLen; i++) {
        if (typeof fns[i] !== 'function') {
          throw TypeError('The argument passed must be a function.')
        }
      }
    
      // 3.定义一个组合之后的函数
      function composeFn(...args) {
        // 3.1.拿到第一个函数的返回值
        let result = fns[0].apply(this, args)
    
        // 3.1.判断传入的函数个数
        if (fnsLen === 1) {
          // 如果传入的函数个数为一个,直接将结果返回
          return result
        } else {
          // 如果传入的函数个数 >= 2
          // 依次将函数取出进行调用,将上一个函数的返回值作为参数传给下一个函数
          // 从第二个函数开始遍历
          for (let i = 1; i < fnsLen; i++) {
            result = fns[i].call(this, result)
          }
    
          // 将结果返回
          return result
        }
      }
    
      // 4.将组合之后的函数返回
      return composeFn
    }
    

    测试:

    function double(num) {
      return num * 2
    }
    function square(num) {
      return num ** 2 
    }
    
    const composeFn = autoComposeFn(double, square)
    const res = composeFn(10)
    console.log(res) // 400
    
     
    分类: JS相关
  • 相关阅读:
    Neutron LBaaS Service(2)—— Neutron Services Insertion Model
    Gevent工作原理(转)
    异步IO/协程/数据库/队列/缓存(转)
    IO多路复用(转)
    pytz库时区的坑(转)
    Python3.0的新特性(原创)
    Dockerfile 中的 CMD 与 ENTRYPOINT(转)
    RESTful及API设计(原创)
    RESTful服务最佳实践(转)
    Flask restful源码分析(原创)
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/16246112.html
Copyright © 2020-2023  润新知