函数式编程(三)—— 柯里化

JavaScriptFunctor

下面我们可以进行函数式编程的基础内容 —— 柯里化。

# 柯里化

解决硬编码的问题

// 下面这段代码是解决了不纯的函数的问题,但是里面出现了硬编码
function checkAge (age) { 
    let mini = 18
    return age >= mini 
}


// 普通的纯函数
function checkAge (min, age) {
    return age >= min
}
console.log(checkAge(18, 20))  //true
console.log(checkAge(18, 24))  //true
console.log(checkAge(20, 24))  //true
// 经常使用18,这段代码是重复的。避免重复
function checkAge (min) {
    return function (age) {
        return age >= min
    }
}

let checkAge18 = checkAge(18)
let checkAge20 = checkAge(20)

console.log(checkAge18(20)) //true
console.log(checkAge18(24)) //true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

柯里化:当函数有多个参数的时候,我们可以对函数进行改造。我们可以调用一个函数,只传递部分的参数(这部分参数以后永远不变),然后让这个函数返回一个新的函数。新的函数传递剩余的参数,并且返回相应的结果。

// ES6
let checkAge = min => (age => age >= min)
// 输出相同
1
2
3

# Lodash中的柯里化 —— curry()

_.curry(func)

  • 功能:创建一个函数,该函数接收一个或多个 func的参数,如果 func 所需要的参数都被提供则执行 func 并返回执行的结果。否则继续返回该函数并等待接收剩余的参数。
  • 参数:需要柯里化的函数
  • 返回值:柯里化后的函数
const _ = require('lodash')

// 参数是一个的为一元函数,两个的是二元函数
// 柯里化可以把一个多元函数转化成一元函数
function getSum (a, b, c) {
  return a + b + c
}

// 定义一个柯里化函数
const curried = _.curry(getSum)

// 如果输入了全部的参数,则立即返回结果
console.log(curried(1, 2, 3)) // 6

//如果传入了部分的参数,此时它会返回当前函数,并且等待接收getSum中的剩余参数
console.log(curried(1)(2, 3)) // 6
console.log(curried(1, 2)(3)) // 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 案例

判断字符串中有没有空白字符,或者提取字符串中所有空白字符,可以使用字符串的match方法: ''.match(/\s+/g)

但是我们要是写一个数组的去处空白字符的方法,上面的代码就无法重用。那我们如何用函数式方法去写

function match(reg, str) {
  return str.match(reg)
}
1
2
3

reg的表达式是重复的,上面的函数如何柯里化,思路是这样的:

//柯里化处理
const _ = require('lodash')

//利用lodash的curry函数,第一个参数是匹配规则,第二个参数是字符串,生成一个match函数
const match = _.curry(function (reg, str) {
  return str.match(reg)
})

// 根据规则haveSpace是一个匹配空格的函数
const haveSpace = match(/\s+/g)

console.log(haveSpace("hello world")) //[ ' ' ]
console.log(haveSpace("helloworld")) //null
// 由此可以判断字符串里面有没有空格

// 那如果是数字的话怎么办呢?
// 根据规则haveNumber是一个匹配数字的函数
const haveNumber = match(/\d+/g)
console.log(haveNumber('abc')) // null

// 对于数组怎么匹配元素中有没有空格
const filter = _.curry(function(func, array) {
  return array.filter(func)
})

// filter函数,第一个参数传递匹配元素中有没有空格
//第二个参数是指定的数组
console.log(filter(haveSpace, ['John Connor','John_Donne'])) // [ 'John Connor' ]

// 如果上述写还是比较麻烦,那么可以再封装一个函数出来
// filter可以传一个参数,然后返回一个函数
// 这个findSpace就是匹配数组元素中有没有空格的函数
const findSpace = filter(haveSpace)
console.log(findSpace(['John Connor','John_Donne'])) // [ 'John Connor' ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

下面对上面的思路做一个小的总结,柯里化的好处就是我们可以最大程度的重用我们的函数

const _ = require('lodash')

//match函数是根据一些正则,匹配字符串,返回匹配结果
const match = _.curry(function (reg, str) {
  return str.match(reg)
})

//haveSpace函数是一个匹配空格的函数
const haveSpace = match(/\s+/g)

//haveNumber函数是一个匹配数字的函数
const haveNumber = match(/\d+/g)

//filter函数是定义一个数组和过滤规则,返回符合匹配规则的数组
const filter = _.curry(function(func, array) {
  return array.filter(func)
})

//findSpace函数是匹配数组元素中有空格并返回符合情况的数组的函数
const findSpace = filter(haveSpace)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 柯里化原理模拟

我们找一个之前做过的例子分析一下

const _ = require('lodash')

function getSum (a, b, c) {
  return a + b + c
}

const curried = _.curry(getSum)

console.log(curried(1, 2, 3))  // 6
console.log(curried(1)(2, 3))  // 6
console.log(curried(1, 2)(3))  // 6
1
2
3
4
5
6
7
8
9
10
11

实现一个柯里化转换函数要进行分析

  1. 入参出参:调用传递一个纯函数的参数,完成之后返回一个柯里化函数
  2. 入参情况分析:
  • 如果curried调用传递的参数和getSum函数参数个数相同,那么立即执行并返回调用结果
  • 如果curried调用传递的参数是getSum函数的部分参数,那么需要返回一个新的函数,并且等待接收getSum的其他参数
  1. 重点关注:
  • 获取调用的参数
  • 判断个数是否相同
// 模拟柯里化函数
function curry (func) {
  // 取名字是为了下面实参小于形参的时候用的
  return function curriedFn(...args) {
    // 判断实参和形参的个数
    if(args.length < func.length) {
      return function() {
        // 等待传递的剩余参数,如果剩余函数的参数加上之前的参数等于形参,那么就返回func
        // 第一部分参数在args里面,第二部分参数在arguments里面,要将两个合并并且展开传递(使用...)
        // concat函数要合并两个数组,arguments为伪数组,所以用Array.from进行转换
        return curriedFn(...args.concat(Array.from(arguments)))
      }
    }
    // 如果实参大于等于形参的个数
    // args是剩余参数,是个数组形式,而返回的时候要展开(使用...)
    return func(...args)
  }
}


// test
const curriedTest = curry(getSum)

console.log(curriedTest(1, 2, 3))  // 6
console.log(curriedTest(1)(2, 3))  // 6
console.log(curriedTest(1, 2)(3))  // 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 柯里化总结

  • 柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数(比如match函数新生成了haveSpace函数,里面使用了闭包,记住了我们给传递的正则表达式的参数)
  • 这是一种对函数参数的'缓存'(使用了闭包)
  • 让函数变的更灵活,让函数的粒度更小
  • 可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能
更新时间: 2021-09-15 12:03