函数式编程-柯里化(currying)和偏应用(partail application 偏函数)

1.9k words

柯里化和偏函数(偏应用,后续称偏函数)的区别在于处理参数上的区别,二者都是函数式编程的一种工具。主要就是方便组合函数的使用,此外理解柯里化偏函数原理是需要闭包函数的前置知识的。

柯里化 currying

柯里化即将一个多元函数转为一元函数的过程。

💡 多元函数

仅接受一个参数的被称作一元函数, 二个参数称作二元函数,以此类推…

1
2
3
4
5
// 一元函数
const double = (n) => n * 2

// 二元函数
const add = (x, y) => x + y

了解完 多元函数 后再来实现 柯里化

🔩 柯里化实现

在js中fn.bind() 方法就可以当做一个柯里化方法来使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const curry = (bindFn, ...firstArg) => {
return (...curArg) => {
// 合并参数
const args = [...firstArg, ...curArg]

// 判断当前的参数个数是否少于函数调用的参数个数
if (args.length < bindFn.length) {
// 记录以传入的参数并返回一个新的函数
return curry(bindFn, ...args)
}

// 执行
return bindFn(...args)
}
}

🌰 使用示例

1
2
3
4
5
6
7
8
9
const add = (x, y) => x + y
/** 对add进行柯里化处理 */
const curryAdd = curry(add)

// 参数个数少于add函数预期,返回一个新的函数
console.log(curryAdd(1)) // [Function (anonymous)]

// 参数个数达到预期,返回结果
console.log(curryAdd(1)(2)) // 3

🍐 其他使用场景

实现一个针对不同情况的系统日志打印。可以这么实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 基础的 logHelper 方法
const logHelper = (type, message) => {
if (type === 'DEBUG') {
console.debug(message)
} else if (type === 'ERROR') {
console.error(message)
} else {
console.log(type, message)
}
}

// 使用柯里化延伸出其他方法
const logDebug = curry(logHelper)('DEBUG')
const logError = curry(logHelper)('ERROR')
const logInfo = curry(logHelper)('INFO')

以上可知 柯里化 所预处理的参数都是,从左至右处理。 如果遇到像 setTimeout 这种,柯里化就无法很好的完成工作。这时候就需要另一个 偏函数

偏函数 partail application

假如要实现一个每隔16ms执行一次的操作,使用 setTimeout(fn, 16)。 这时候柯里化固定的就是第一个参数fn,解决这种情况使用的是偏函数

🌰 使用示例

1
2
3
4
5
6
7
8
// 使用 undefined 充当占位符
const nextAnimeFrame = partial(setTimeout, undefined, 16)

/** 执行 */
console.log('第一次', Date.now())
nextAnimeFrame(() => {
console.log('第二次', Date.now())
})

🔩 偏函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const partial = (fn, ...partialArg) => {
return (...fullArg) => {
// 复制一份预存的参数
const args = [...partialArg]
// 当前填充fullArg的下标位置
let arg = 0

for (let i = 0; i < fn.length && arg < fullArg.length; i++) {
// 使用 undefined 充当占位符
if (args[i] === undefined) {
// 填充新传入的参数
args[i] = fullArg[arg++]
}
}

// 执行
return fn(...args)
}
}