# 柯里化与偏应用
# 柯里化
柯里化是把一个多参数函数转换为一个嵌套的一元函数的过程。
简单演示柯里化:
// 非柯里化
const add = (x, y) => x + y
// 将上边的函数柯里化
const addCurried = x => y => x + y
1
2
3
4
5
2
3
4
5
# curry 函数
curry 函数实现(只能处理两个参数):
const curry = binaryFn => {
return function (firstArg) {
return function (secondArg) {
return binaryFn(firstArg, secondAry)
}
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
使用 curry 函数将上边 add 函数转换为一个柯里化版本:
let autoCurriedAdd = curry(add)
autoCurriedAdd(2)(6) // 8
1
2
2
完善 curry 函数:
let curry = fn => {
if (typeof fn !== 'function') {
throw Error('No function provided')
}
return function curriedFn (...args) {
return fn.apply(null, args)
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
使用完善的 curry 函数:
const multiply = (x, y, z) => x * y * z
curry(multiply)(1, 2, 3) // 2
curry(multiply)(1, 2, 0) // 0
1
2
3
4
2
3
4
完善后的 curry 函数不符合定义将多参数函数转换为嵌套的一元函数,继续完善 curry 函数:
let curry = fn => {
if (typeof fn !== 'function') {
throw Error('No function provided')
}
return function curriedFn (...args) {
if (args.length < fn.length) {
return function () {
return curriedFn.apply(null, args.concat([].slice.call(arguments))) // 注意这里第二个参数,concat 将传入的参数与原有的参数拼接为新的数组
}
}
return fn.apply(null, args)
}
}
// 使用 es6 改写 curry 方法
const curry = func => {
const g = (...allArgs) => allArgs.length >= func.length ?
func(...allArgs)
: (...args) => g(...allArgs, ...args)
return g
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
使用完成后的 curry:
let multiply = (x, y, z) => x * y * z
curry(multiply)(3)(2)(1)
1
2
3
2
3
# 利用 curry 处理日志问题
const loggerHelper = (mode, initialMessage, errorMessage, lineNo) => {
if (mode === 'DEBUG') {
console.debug(initialMessage, errorMessage + 'at line:' + lineNo)
} else if (mode === 'ERROR') {
console.error(initialMessage, errorMessage + 'at line:' + lineNo)
} else if (mode === 'WARN') {
console.warn(initialMessage, errorMessage + 'at line:' + lineNo)
} else {
throw 'Wrong mode'
}
}
// 正常使用 loggerHelper:
loggerHelper('ERROR', 'Error At Stats.js', 'Invalid argument passed', 23)
// 下面使用 curry 解决重复使用前两个参数的问题:
let errorLogger = curry(loggerHelper)('Error')('Error At Atats.js')
let debugLogger = curry(loggerHelper)('DEBUG')('Debug At Atats.js')
let warnLogger = curry(loggerHelper)('WARN')('Warn At Atats.js')
// 用于错误:
errorLogger('Error Message', 21)
// 用于调试:
debugLogger('Debug Message', 21)
// 用于警告:
warnLogger('Warn Message', 21)
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
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
# 柯里化实战
# 在数组内容中查找数字
let match = curry(function (expr, str) {
return str.match(expr)
})
let hasNumber = match(/[0-9]+/)
console.log(hasNumber('4563fdff'))
1
2
3
4
5
6
2
3
4
5
6
# 柯里化的 filter 函数
let filterAry = curry(function (f, ary) {
return ary.filter(f)
})
1
2
3
2
3
组合使用 hasNumber 以及 filterAry
let findNumbersInArray = filter(hasNumber)
findNumbersInArray(['js', 'number3'])
1
2
3
2
3
# 求数组的平方
let map = curry(function (f, ary) {
return ary.map(f)
})
let squareAll = map((x) => x * x)
squareAll([1, 2, 3])
1
2
3
4
5
6
7
2
3
4
5
6
7
# 数据流
# 偏函数
允许开发者部分地应用函数参数。代码实现:
const partial = function (fn, ...partialArgs) {
let args = partialArgs
return function (...fullArguments) {
let arg = 0
for (let i = 0; i < args.length && arg < fullArguments.length; i++) {
if (args[i] === undefined) {
args[i] = fullArguments[arg++] // 注意这里的 arg++ 先返回值了,然后相加
}
}
return fn.apply(null, args)
}
}
// 快速使用 partial 实现延迟执行
let delayTenMs = partial(setTimeout, undefined, 10)
delayTenMs(() => console.log('10 ms later logger'))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
美化 JSON 输出(JSON.stringify 文档):
let obj = {name: 'newming', age: 12}
JSON.stringify(obj, null, 4)
1
2
2
这里我们使用 JSON.stringify 函数调用的最后两个参数总是相同的,'null, 2',可以使用 partial 移除样板代码:
let prettyPrintJson = partial(JSON.stringify, undefined, null, 2) // 这里 null, 2 已经被固定应用,只需再次调用传入 undefined 的值即可
prettyPrintJson({name: 'newming', age: 12})
1
2
3
2
3
这里的偏函数实现中有个 bug,例如再次调用 delayTenMs 传入不同的参数,打印的结果仍为第一次执行 delayTenMs 的结果,原因是因为在修改 args 中 undefined 的值,利用的是数组,而数组传递的是引用。