• 1

  • 508

函数式编程指南第一弹

1个月前

什么是函数式编程?

面向对象的编程思维方式:把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和多态来演示事物事件的联系。

函数式编程的思维方式:把现实世界的事物和事物之间的联系抽象到程序世界

函数式编程是范畴论,范畴论是数学分支的一门很复杂的数学,彼此之间存在关系概念。箭头表示范畴成员之间的关系,名字被成为“态射”,通过“态射”,一个成员能够变形成另一个成员。所有的成员是一个集合,变形关系就是函数。y=f(x)

函数式编程是20世纪三十年代引入的一套用于研究函数定义、函数应用和递归的形式系统,函数式编程也并非就是用函数来编程,主旨是将复杂的函数复合成简单的函数。函数式编程的真正在前端圈中火热的原因 可能是来源于react中的高阶函数。

函数是一等公民

  • 函数是一等公民指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他函数,可以作为参数,可以作为其他函数的返回值。
  • 不可改变变量,在函数式编程中变量仅仅代表某一个表达式,变量是不能被修改的,所有的变量只能被赋一次初始值。

  • map和reduce就是最常用的函数式编程

函数式编程的特点

  1. 函数是一等公民

  2. 只用表达式,不用语句

  3. 没有副作用

  4. 引用透明(函数运行只靠参数)

声明式代码和命令式代码

命令式代码就是编写一条一条的指令去解决我们的需求

// 命令式
var fruitsName = [];
for (i = 0; i < fruits.length; i++) {
  fruitsName.push(fruits[i].name);
}
  // 声明式 var fruits = cars.map(fruit=>fruit.name); 复制代码

函数式编程就是使用这种声明式代码

纯函数

什么是纯函数呢?对于相同的输入,得到的一定是相同的输出,而且没有任何可以观察的副作用。

举个例子:

var a = 5

function Sum(b){
  return a + b
}
Sum(5) 复制代码

如上图列子,它是一个纯函数吗?显然不是的。变量a是可以被修改的。我们稍作修改

const a = 5

function Sum(b){
 return a + b 
}
 Sum(5) 复制代码

这是纯函数,确定的输入,确定的输出。但不是最佳的函数,原因是它依赖了外部的环境。怎么样解决这个问题,我们看下面的这个例子。

function A(a){
 return function (b){
    return a + b
    }
}
 var B = A(5) B(5) 复制代码

首先来看函数A ,每次输入一个参数,得到的函数都是固定的,所以A函数是纯函数,再来看函数B,乍一看,好像是依赖了外部变量a,但是这个a每次都是在A函数调用之后产生的,并且调用之后是无法改变的。这样看来B函数也是一个纯函数。或许有的人会想,那我在调用一次A函数,传一个6进去,那不就改变了吗?首先你在掉一次A函数,产生的函数就不是B函数了,或许是C,或许是D。反正不是之前的那个B函数了。

高阶函数

  • 函数当参数,把传入的函数做一个封装,然后返回这个封装 函数,达到更高程度的抽象。

function forEach (array,fn){
 for(let i = 0 ; i < array.length ; i++){
       fn(array[i])
    }
}
 forEach(arr,function(item){  console.log(item) }) 复制代码
  • 函数作为返回值

function once(fn){
 let done = false 
    return function(){
     if(!done){
         done = true 
 return fn.apply(this,arguments)  }  } }  let fn = once(function(){  consnole.log('我只会执行一次!!') }) fn() fn() fn() 复制代码

如上的例子可以很好的看出高阶函数的应用场景。还有react中的高阶组件,本质上也是高阶函数的一种扩展。

使用高阶函数的意义

  • 抽象可以帮我们屏蔽细节,只需要关注与我们的目标

  • 高阶函数是用来抽象通用的问题

常用的高阶函数

  • forEach

  • map

  • filter

  • every

  • some

  • find/findIndex

  • reduce

  • sort ...

函数的柯里化

当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后不变)

然后返回一个新的函数去接收处理剩余的参数,返回结果

如上AB函数就是柯里化实现的一个例子。在接收了一个参数a,返回一个函数去处理剩下的参数b。假如有一个正则匹配的函数数。你可能会这样写。

function checkByRegExp(regExp,str){
 return regExp.test(str)
}

checkByRegExp(/^1\d{10}$/, '13333333333'); // 校验手机
 checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, '12@qq.com'); // 校验邮箱  复制代码

但是如果校验的手机次数多了之后,会发现正则需要写好多次。

checkByRegExp(/^1\d{10}$/, '13333333333');
checkByRegExp(/^1\d{10}$/, '12222222222');
复制代码

显然,程序员骨子里的优雅是不允许我这么做的,此处就可以用函数柯里化来优化它。

function checkByRegExp(regExp){
 return function(str){
    return regExp.test(str)
   }
}
const checkPhone = checkByRegExp(/^1\d{10}$/) checkPhone('13333333333') checkPhone('12222222222') 复制代码

某种意义上来讲,柯里化是一种对参数的“缓存”,是一种非常高效的编写函数的方法。

lodash中的柯里化方法

  • _curry(func)

功能: 创建一个函数,该函数接收一个或多个func的参数,如果func所需要的参数都被提供则执行func并返回执行的结果。否则返回该函数并等待接收剩余的参数

参数:需要柯里化的函数

返回值:柯里化后的函数

const _ = require('lodash')
function getSum(a,b,c){
  return a + b + c
}
let curried = _.curry(getSum)
 curried(1)(2)(3) curried(1,2,3) curried(1,2)(3) 复制代码

总结

  • 柯里化可以让我们给一个函数 传递较少的参数得到一个已经记住了某些固定参数的新函数

  • 这是一种对参数的‘缓存’

  • 让函数变的更灵活,让函数的粒度更小

  • 可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能

注: 多元函数指的就是多个参数的函数

函数组合

为了解决函数嵌套的问题,避免写出洋葱式代码

// 翻转数组
const reverseArr = x => x.reverse()
// 取第一个元素
const first = x => x[0] 

//那我们取出第最后一个元素的代码将会是这个样子的 first(reverseArr([1,2,3,4,5])) 复制代码

假如我们有个函数,取出一个数组最后一个元素。首先这个函数有两个步骤,1.翻转数组,2.拿出数组第一个元素。如上代码在调用之后变成洋葱式代码 h(g(f(x)))  ,为了解决这个问题,把代码拉平。我们就需要用到函数组合。

const compose = (f,g)=>(x=>f(g(x)))

const getLastValue = compose(first,reverseArr)

getLastValue([1,2,3,4,5])
复制代码

我们虽然写了很多代码来实现取出数组最后一个元素的功能,但是大家别忘记了我们写的这些辅助函数可以任意的组合。函数式编程可以最大程度上使得函数被重用

lodash中的组合方法

  • lodash中的组合函数flow()或者flowRight(),他们都可以组合多个函数

  • flow()是从左到右运行

  • flowRight()是从右到左运行,使用的更多一些

const _ = require('lodash')

const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()
 const fn = _.flowRight(toUpper,first,reverse)  console.log(fn(['one','two','three']))  复制代码

函数组合结合律

函数组合需要满足结合律

我们既可以把g和h组合,还可以把f和g组合,结果都是一样的

//结合律
let f = compose (f,g,h)
let associative = compose(compose(f,g),h) === compose(f,compose(g,h))
// true
复制代码

PointFree风格

是函数式编程的一种风格,可以把数据处理的过程定义成与数据无关的合成运算。不需要用到代表数据的那个参数,只要把简单的运算步骤合成一起,然后通过组合函数进行组合。

  • 不需要指明处理的数据

  • 只需要合成运算过程

  • 需要定义一些辅助的基本运算函数

const fn = str => str.toUpperCase().split(' ')
fn('pointfree')
复制代码

在这个函数中,我们使用了str这个变量,毫无意义。那么如何用pointFree风格去改变它呢。

const split = x => x.split(' ')

const toUpperCase = x => x.toUpperCase()

const fn = compose(split,toUpperCase)
 fn('pointfree')  复制代码

首先将两个原生的方法改造成纯函数,然后就可以将两个方法组合在一起。可以让这两个方法复用性更强。

总结

本章内容带大家基本了解了一下函数式编程的概念,当然这只是开胃菜😏,下章带大家深入函数式编程中的函子。如果文章对大家有用,还麻烦大家动动手指点个赞哦!

本文使用 mdnice 排版

免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

程序员

508

相关文章推荐

未登录头像

暂无评论