函数式编程-柯里化、偏函数、组合、管道
柯里化
柯里化是把一个多参数函数转换成一个嵌套的一元函数的过程。
比如如下函数:
const add = (x, y) => x + y
假设有很多场景下,其中一个值都是不会改变的,如add(1, 2)
, add(1, 3)
, add(1, 4)
。这种情况下可以使用柯里化的方式,简化函数调用。
function curry (fn, val1) {
return (val2) => {
return fn(val1, val2)
}
}
const addCurried = curry(add, 1)
addCurried(2) // add(1, 2)
addCurried(3) // add(1, 3)
柯里化本质上是应用了闭包的原理,在内存中持有设置的初始变量与函数,在实际调用中调用原始函数,并将柯里化时传入的值与调用时传入的值都传入原始函数。
但是上面实现的柯里化函数仅支持示例函数add
,使用柯里化的概念,也可以实现适配所有函数调用的方法。
function curry(fn, ...args) {
if (typeof fn !== 'function') {
throw Error('No function provided')
}
return (...args2) => {
return fn(...args, ...args2)
}
}
偏函数
偏函数,又称作部分应用函数,它允许开发者部分地应用函数参数。
实际上偏函数与柯里化概念类似,但是柯里化只能省略前面(或者后面)的参数,而偏函数是预先提供部分参数,从而在调用时可以省略这些参数。
const partial = (fn, ...partialArgs) => {
return (...args) => {
let count = 0
for (let i = 0; i < partialArgs.length && count < args.length; i++) {
if (partialArgs[i] === undefined) {
partialArgs[i] = args[count++]
}
}
return fn.apply(null, partialArgs)
}
}
如上函数则是一个偏函数的实现,其相比于柯里化可以灵活的设置需要预先传入的值,由实际调用时传的值则通过传入undefined
占位。以下是一个使用示例:
function log(a, b, c, d) {
console.log('log: ', a, b, c, d)
}
const partialLog = partial(log, 1, undefined, undefined, 4)
partialLog(2, 3) // log: 1 2 3 4
管道
函数式编程就是将每个不同的计算部分分散封装到不同的函数内,等到使用的时候再一一拿出来并按照一定的顺序进行执行,就类似于管道一样,共同组合成一个完整的函数。
函数式编程里不得不提的一个函数式编程的概念就是函数管道,其本质是通过多个单参函数组合起来,形成一个新的函数。即前一个函数的返回值会作为后一个函数的入参。
function pipe() {
const funs = Array.from(arguments)
return (val) => {
return funs.reduce((pre, fun) => fun(pre), val)
}
}
function toString(val) {
return '' + val
}
function addOne(val) {
return val + ' one'
}
function addTwo(val) {
return val + ' two'
}
const pipeFuntion = pipe(toString, addOne, addTwo)
pipeFuntion('hello') // hello one two
以上是一个简单的管道函数写法,其本质是通过闭包,将不同的函数组合,然后通过函数数组的reduce
进行逐个调用,并返回其返回值。
但是函数管道仅支持单参数函数,原因是由于函数的返回值只有一个,如果有多个参数的函数需要加进管道,则需要借助柯里化或偏函数的方式预先设置其余参数。
组合
组合其实跟管道的概念与实现基本一致,只不过管道的调用是从左到右,而组合的调用是从右到左。一个简单的组合例子如下:
const compose = (a, b) => (val) => a(b(val))
参照管道的实现,只需要简单的将函数数组进行一次倒序后再调用,则可以实现通用的组合函数:
function compose() {
const funs = Array.from(arguments)
return (val) => {
return funs.reverse().reduce((pre, fun) => fun(pre), val)
}
}
函数式编程倾向于用一系列嵌套的函数来描述运算过程,强调在编程的时候用函数的方式思考问题,在这种编程模式中最常用的函数和表达式。
参考
JavaScript ES6函数式编程(二):柯里化、偏应用、组合、管道
JavaScript进阶 → 函数式与面向对象
Pointfree 编程风格指南