函数柯里化
函数柯里化的基本概念
函数柯里化是一种将多参数函数转换为一系列单参数函数的技术。它由数学家Haskell Curry命名,核心思想是把接受多个参数的函数变换成接受单一参数的函数,并返回接受余下参数的新函数。
// 普通加法函数
function add(a, b) {
return a + b
}
// 柯里化后的加法函数
function curriedAdd(a) {
return function(b) {
return a + b
}
}
console.log(add(1, 2)) // 3
console.log(curriedAdd(1)(2)) // 3
柯里化的实现原理
实现柯里化的关键在于函数递归和参数收集。当传入的参数数量不足时,返回一个新函数继续收集参数;当参数数量足够时,执行原始函数。
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args)
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
}
// 使用示例
function sum(a, b, c) {
return a + b + c
}
const curriedSum = curry(sum)
console.log(curriedSum(1)(2)(3)) // 6
console.log(curriedSum(1, 2)(3)) // 6
console.log(curriedSum(1, 2, 3)) // 6
柯里化的高级应用
柯里化不仅仅是参数转换,它还能实现函数组合和延迟执行等高级功能。
参数复用
// 创建特定前缀的日志函数
function log(level, message) {
console.log(`[${level}] ${message}`)
}
const curriedLog = curry(log)
const debugLog = curriedLog('DEBUG')
const errorLog = curriedLog('ERROR')
debugLog('This is a debug message') // [DEBUG] This is a debug message
errorLog('Something went wrong!') // [ERROR] Something went wrong!
函数组合
function compose(...fns) {
return fns.reduce((f, g) => (...args) => f(g(...args)))
}
const toUpper = str => str.toUpperCase()
const exclaim = str => `${str}!`
const shout = compose(exclaim, toUpper)
console.log(shout('hello')) // "HELLO!"
// 柯里化版本
const curriedCompose = (...fns) => x => fns.reduceRight((v, f) => f(v), x)
const shout2 = curriedCompose(exclaim, toUpper)
console.log(shout2('world')) // "WORLD!"
柯里化的实际应用场景
事件处理
// 普通事件处理
document.querySelector('#btn').addEventListener('click', (event) => {
sendAnalytics('click', event.target.id)
})
// 柯里化版本
const sendEvent = curry((type, id, event) => {
sendAnalytics(type, id)
})
document.querySelector('#btn').addEventListener('click', sendEvent('click', 'btn'))
API请求
// 普通API请求
function fetchApi(method, endpoint, data) {
return fetch(endpoint, {
method,
body: JSON.stringify(data)
})
}
// 柯里化版本
const curriedFetch = curry(fetchApi)
const get = curriedFetch('GET')
const post = curriedFetch('POST')
const getUsers = get('/api/users')
const createUser = post('/api/users')
// 使用
getUsers().then(/* ... */)
createUser({ name: 'John' }).then(/* ... */)
柯里化的性能考量
虽然柯里化提供了很多便利,但也需要考虑其性能影响:
- 每次柯里化都会创建新的函数对象,可能增加内存消耗
- 深层嵌套的柯里化调用可能导致调用栈增加
- 在性能敏感的场景中需要谨慎使用
// 性能测试示例
function testPerformance() {
const normalAdd = (a, b, c, d) => a + b + c + d
const curriedAdd = curry(normalAdd)
console.time('normal')
for (let i = 0; i < 1000000; i++) {
normalAdd(1, 2, 3, 4)
}
console.timeEnd('normal')
console.time('curried')
for (let i = 0; i < 1000000; i++) {
curriedAdd(1)(2)(3)(4)
}
console.timeEnd('curried')
}
testPerformance()
// 正常调用通常比柯里化调用快2-5倍
柯里化与部分应用的比较
柯里化和部分应用(Partial Application)经常被混淆,但它们有本质区别:
- 柯里化:将多参数函数转换为嵌套的单参数函数
- 部分应用:固定一个函数的部分参数,产生一个更小元的函数
// 部分应用实现
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn(...presetArgs, ...laterArgs)
}
}
// 使用示例
function greet(greeting, name, punctuation) {
return `${greeting}, ${name}${punctuation}`
}
const sayHello = partial(greet, 'Hello')
const sayHelloToJohn = partial(greet, 'Hello', 'John')
console.log(sayHello('John', '!')) // "Hello, John!"
console.log(sayHelloToJohn('!')) // "Hello, John!"
在函数式编程库中的应用
主流函数式编程库如Ramda和Lodash都提供了完善的柯里化支持。
Ramda的自动柯里化
import R from 'ramda'
const addFourNumbers = (a, b, c, d) => a + b + c + d
const curriedAdd = R.curry(addFourNumbers)
const f = curriedAdd(1, 2)
const g = f(3)
g(4) // 10
Lodash的柯里化
import _ from 'lodash'
function greet(greeting, name) {
return greeting + ' ' + name
}
const curriedGreet = _.curry(greet)
const sayHello = curriedGreet('Hello')
console.log(sayHello('Alice')) // "Hello Alice"
柯里化的变体实现
除了标准柯里化外,还有一些变体实现方式:
无限参数柯里化
function infiniteCurry(fn) {
return function curried(...args) {
if (args.length === 0) {
return curried
}
return (...args2) => {
if (args2.length === 0) {
return fn(...args)
}
return curried(...args, ...args2)
}
}
}
const sum = infiniteCurry((...nums) => nums.reduce((a, b) => a + b, 0))
console.log(sum(1)(2)(3)()) // 6
console.log(sum(1, 2)(3, 4)(5)()) // 15
占位符柯里化
function curryWithPlaceholder(fn) {
return function curried(...args) {
const complete = args.length >= fn.length && !args.includes(curryWithPlaceholder._)
if (complete) {
return fn.apply(this, args)
}
return function(...args2) {
const combined = []
let i = 0
for (const arg of args) {
combined.push(arg === curryWithPlaceholder._ && i < args2.length ? args2[i++] : arg)
}
while (i < args2.length) {
combined.push(args2[i++])
}
return curried.apply(this, combined)
}
}
}
curryWithPlaceholder._ = Symbol('placeholder')
// 使用示例
const curriedFn = curryWithPlaceholder(function(a, b, c, d) {
return [a, b, c, d]
})
const _ = curryWithPlaceholder._
console.log(curriedFn(1)(2)(3)(4)) // [1, 2, 3, 4]
console.log(curriedFn(_, 2)(1, _, 4)(3)) // [1, 2, 3, 4]
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn