函数式编程主食

函数式编程是什么?html

在函数式编程中,函数就是一个管道(pipe)。这头进去一个值,那头就会出来一个新的值,没有其余做用。——阮一峰 函数式编程入门教程ajax

函数式编程本质上是一种数学运算。由于是数学运算因此天然就会涉及到加减乘除等运算和交换律结合律同一概分配律等运算法则。若是要函数顺利的进行数学运算,就要求函数必须是纯的,不能有反作用,即纯函数。但若是只是简单的将纯函数用于复杂的加减乘除运算,则会写出一堆看起来杂乱无章的、不符合人类阅读习惯和编码直觉的代码。所以函数式编程须要借助组合函数、柯里化、递归、闭包和各类各样的高阶函数让代码看起来更符合人类的直觉和逻辑思惟的方式。算法

再说一等公民

当咱们在说函数是“一等公民”的时候,不要想固然的觉得函数就是 js 世界里的老大了,咱们实际上说的是它们和其余对象都同样:就是普通公民。编程

做为一等公民,函数能够被赋值给另一个变量,然而编程中却有不少这样的神奇操做:json

const hi = name => `Hi, ${name}`
const greeting = name => hi(name)
复制代码

实际上调用 hi('girl')greeting('gril') 的结果不管如何都是彻底同样的,greeting 函数所做的不过是调用并返回 hi 函数而已。但实际上彻底不必如此脱裤子放屁画蛇添足,直接把 hi 函数赋值给 greeting 变量便可:数组

const hi = name => `Hi, ${name}`
const greeting = hi
复制代码

懂了吗?那再看下下面这个:缓存

// 太傻了
const getServerStuff = callback => ajaxCall(json => callback(json)) // ajaxCall 是外部封装好的接口函数
复制代码

这么长的函数,看都看不懂,咱们来简化下:闭包

// 这行
ajaxCall(json => callback(json));

// 等价于这行
ajaxCall(callback);

// 那么,重构下 getServerStuff
const getServerStuff = callback => ajaxCall(callback);

// ...就等于
const getServerStuff = ajaxCall // <-- 看,没有括号哦
复制代码

拆到底就是个赋值操做而已~ 必定要避免这种气人又尴尬的函数啊~app

纯函数

纯函数是这样一种函数,即相同的输入,永远会获得相同的输出,并且没有任何可观察的反作用。函数式编程

从这句话中能够看出要成为纯函数有两个充分必要条件:

  1. 相同的输入会获得相同的输出(也叫引用透明性)。好比如果函数的输入参数是数字返回值是数组,就不会发生输入参数是数字返回值是数字的状况。
  2. 没有任何可观察的反作用。也就是说函数在执行的过程当中不依赖于外部的状态 / 也不会改变外部的状态。

好比对于 slicesplice 函数,输入数字参数,总能返回数组。可是不一样之处在于 splice 在执行过程当中会改变原数组,而 slice 在没有这样的反作用。因此 slice 是纯函数而 splice 不是,以下所示:

let nums = [1, 2, 3, 4, 5]
let a = nums.slice(0,2) // [1, 2]
console.log(nums) // [1, 2, 3, 4, 5]

let nums = [1, 2, 3, 4, 5]
let b = nums.splice(0, 2) // [1, 2]
console.log(nums) // [3, 4, 5]
复制代码

戏剧性的是:纯函数就是数学上的函数,并且是函数式编程的所有。

如何才能编写无反作用的纯函数是学习函数式编程的关键。

由于不依赖外部的状态,因此纯函数编程须要借助一些工具函数,来使函数的传参看起来优雅且容易理解。

声明式编程

函数式编程属于声明式编程范式,函数式编程的目的是使用函数来抽象做用在数据之上的控制流和操做,从而在系统中消除反作用并减小对状态的改变。

// 命令式方式
var array = [0, 1, 2, 3]
for(let i = 0; i < array.length; i++) {
    array[i] = Math.pow(array[i], 2)
}
array // [0, 1, 4, 9]

// 声明式方式
[0, 1, 2, 3].map(num => Math.pow(num, 2)) // [0, 1, 4, 9]

复制代码

命令式编程注重代码执行过程,上面代码使用了命令控制结构 for 循环,正是这种命令控制语句致使了代码的死板和难以复用。函数式编程不关注代码具体如何执行和数据如何穿过函数,而关注代码执行结果。这也决定了函数式编程须要倚重一些工具函数( 如 map filter ruduce find 等数组函数和 curry compose 等工具函数 )。

柯里化

俗话说一口吃不成胖子,记得第一次接触柯里化是在红宝书里面,当时看了好几遍仍是一脸懵逼,不知所云。因此柯里化函数是须要必定能力的抽象思惟的。

柯里化是一种将使用多个参数的函数转换成一系列使用一个或多个参数的函数的技术。你能够一次性地传递全部参数调用函数,也能够每次只传一个参数分屡次调用。

function sub_curry(fn, ...args) { // sub_curry 用来缓存传入的参数
  return function() {
    return fn.apply(this, args.concat([...arguments])
  }
}
function curry(fn, length) {
  length = length || fn.length
  return function(...args) {
    if (args.length < length) {
      var combined = [fn].concat(args)
      return curry(sub_curry.apply(this, combined), length - args.length) // 递归调用自身返回一个固定部分参数的函数
    } else {
      return fn.apply(this, args)
    }
  }
}
var fn = curry(function(a, b, c) {
  return [a, b, c]
})
fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]
复制代码

curry 函数的原理是利用闭包将原函数的一部分参数存起来,若是参数小于原函数形参的数量就返回一个新函数以便继续传参调用,若是参数等于原函数的参数了就执行原函数。

import { curry } from 'lodash'

function add(a, b) {
  return a + b
}
var addCurry = curry(add) // 柯里化add

var increment = addCurry(1) // 生成新函数 increment 执行会将入参增长 1
increment(10) // 11

var addTen = addCurry(10)) // 生成新函数 increment 执行会将入参增长 1
addTen(1) // 生成新函数 addTen 执行会将入参增长 10
复制代码

能够看到利用柯里化技术,可以生成固定了部分参数的新函数。

组合

咱们认为组合是高于其余全部原则的设计原则,这是由于组合让咱们的代码简单而富有可读性。

组合顾名思义就是将不一样的函数组合起来生成一个新的函数。组合的参数必须都是纯函数。

function compose (...fns) {
  return function (...args) {
    return fns.reduceRight((arg , fn, index) => {
      if (index === fns.length - 1) {
        return fn(...arg)
      }
      return fn(arg)
    }, args)
  }
}

function toUpperCase(str) {
    return str.toUpperCase()
}
function split(str){
  return str.split('');
}
function reverse(arr){
  return arr.reverse();
}
function join(arr){
  return arr.join('');
}

const turnStr = compose(join, reverse, split, toUpperCase)
turnStr('emosewa si nijeuj') // JUEJIN IS AWESOME
复制代码

组合函数好像一个管道同样,将参数从右向左依次执行并将结果传递给左边的参数。

组合函数还符合结合律,组合的调用分组不重要,全部结果都是同样的:

const turnStr1 = compose(compose(join, reverse), split, toUpperCase)
turnStr1('emosewa si nijeuj') // JUEJIN IS AWESOME

const turnStr2 = compose(compose(join, reverse), split, toUpperCase)
turnStr2('emosewa si nijeuj') // JUEJIN IS AWESOME
复制代码

结合律的一大好处是任何一个函数分组均可以被拆开来,而后再以它们本身的组合方式打包在一块儿。固然这个就看我的的抽象能力和编码能力了。

总结

函数式编程不是一朝一夕就能学会的,而是要在实际开发中逐渐学习和熟练的。在实际编程中,尽可能的利用 curry compose 等工具函数和递归将代码控制逻辑抽先化,逐渐舍弃命令式开发而习惯编写声明式的代码。

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息