Function currying
The Basic Concept of Function Currying
Function currying is a technique that transforms a multi-parameter function into a series of single-parameter functions. Named after the mathematician Haskell Curry, its core idea is to convert a function that accepts multiple parameters into one that takes a single parameter and returns a new function to accept the remaining parameters.
// Normal addition function
function add(a, b) {
return a + b
}
// Curried addition function
function curriedAdd(a) {
return function(b) {
return a + b
}
}
console.log(add(1, 2)) // 3
console.log(curriedAdd(1)(2)) // 3
The Implementation Principle of Currying
The key to implementing currying lies in function recursion and parameter collection. When the number of passed parameters is insufficient, a new function is returned to continue collecting parameters; when enough parameters are provided, the original function is executed.
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))
}
}
}
}
// Usage example
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
Advanced Applications of Currying
Currying is not just about parameter transformation; it can also enable advanced functionalities like function composition and deferred execution.
Parameter Reuse
// Create log functions with specific prefixes
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 Composition
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!"
// Curried version
const curriedCompose = (...fns) => x => fns.reduceRight((v, f) => f(v), x)
const shout2 = curriedCompose(exclaim, toUpper)
console.log(shout2('world')) // "WORLD!"
Practical Use Cases of Currying
Event Handling
// Normal event handling
document.querySelector('#btn').addEventListener('click', (event) => {
sendAnalytics('click', event.target.id)
})
// Curried version
const sendEvent = curry((type, id, event) => {
sendAnalytics(type, id)
})
document.querySelector('#btn').addEventListener('click', sendEvent('click', 'btn'))
API Requests
// Normal API request
function fetchApi(method, endpoint, data) {
return fetch(endpoint, {
method,
body: JSON.stringify(data)
})
}
// Curried version
const curriedFetch = curry(fetchApi)
const get = curriedFetch('GET')
const post = curriedFetch('POST')
const getUsers = get('/api/users')
const createUser = post('/api/users')
// Usage
getUsers().then(/* ... */)
createUser({ name: 'John' }).then(/* ... */)
Performance Considerations of Currying
While currying offers many conveniences, its performance impact should also be considered:
- Each currying operation creates a new function object, potentially increasing memory consumption.
- Deeply nested currying calls may increase the call stack.
- Use with caution in performance-sensitive scenarios.
// Performance test example
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()
// Normal calls are typically 2-5 times faster than curried calls
Comparison Between Currying and Partial Application
Currying and partial application are often confused, but they have fundamental differences:
- Currying: Converts a multi-parameter function into nested single-parameter functions.
- Partial Application: Fixes some parameters of a function to produce a lower-arity function.
// Partial application implementation
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn(...presetArgs, ...laterArgs)
}
}
// Usage example
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!"
Applications in Functional Programming Libraries
Mainstream functional programming libraries like Ramda and Lodash provide comprehensive currying support.
Automatic Currying in 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
Currying in 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"
Variant Implementations of Currying
Beyond standard currying, there are some variant implementations:
Infinite Parameter Currying
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
Placeholder Currying
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')
// Usage example
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