柯里化函数

318 阅读2分钟

为什么要使用柯里化函数

上一篇文章提到函数式编程很容易写出洋葱代码,不好维护,柯里化函数能解决这个问题

什么是柯里化函数

柯里化函数是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

  • 当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后永远不变)
  • 然后返回一个新的函数接收剩余的参数,返回结果

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 _ = require('lodash')
const match = _.curry(function (reg, str) {
  return str.match(reg)
})
const haveSpace = match(/\s+/g)
const filter = _.curry(function (func, array) {
  return array.filter(func)
})
const findSpace = filter(haveSpace)
console.log(findSpace(['John Connor', 'John_Donne']))

模拟_.curry()的实现

function curry(func) {
  return function curriedFn(...args) {
    // 判断实参和形参的个数 
    if (args.length < func.length) {
      return function () {
        return curriedFn(...args.concat(Array.from(arguments)))
      }
    }
    // 实参和形参个数相同,调用 func,返回结果 
    return func(...args)
  }
}

函数组合

  • 纯函数和柯里化很容易写出洋葱代码 h(g(f(x))) image.png
  • 函数组合可以让我们把细粒度的函数重新组合生成一个新的函数

管道:补充知识

下图数据a通过管道得到数据b

graph LR
a --> fn --> b

下面这张图中可以想象成把 fn 这个管道拆分成了3个管道 f1, f2, f3,数据 a 通过管道 f1 得到结果 m,m 再通过管道 f2 得到结果 n,n 通过管道 f3 得到最终结果b

graph LR
a --> f1 --> f2 --> f3 --> b
fn = compose(f1, f2, f3)
b = fn(a)

函数组合

// 组合函数初级版
const compose = (f, g) => x => f(g(x))
// 多函数组合 默认是从右到左执行
// acc:上一次调用返回值, fn:fns数组中当前被处理的元素
const compose = (...fns) => 
  value => fns.reduceRight((acc, fn) => fn(acc), value)
// 案例:输出数组最后一项的大写
const first = arr => arr[0]
const reverse = arr => arr.reverse()
const toUpper = s => s.toUpperCase()

const lastItemToUpper = compose(toUpper, first, reverse)
console.log(lastItemToUpper(['apple', 'banana', 'grape']))

函数的组合要满足结合律

const lastItemToUpper = compose(compose(toUpper, first), reverse)
const lastItemToUpper = compose(toUpper, compose(first, reverse))

调试

const trace = curry((tag, v) => {
  console.log(tag, v)
  return v
})
const lastItemToUpper = compose(
    toUpper,
    trace('first之后'),
    first,
    trace('reverse之后'),
    reverse
)

实际应用

lodash 的 fp 模块提供了实用的对函数式编程友好的方法

const fp = require('lodash/fp')
const f = fp.flowRight(fp.join('-'), fp.map(_.toLower), fp.split(' '))
console.log(f('NEVER SAY DIE'))

在实际开发过程中可以采用小步快跑的形式,将函数式编程,柯里化函数逐渐应用到实际开发中。