阿里云主机折上折
  • 微信号
Current Site:Index > Function currying

Function currying

Author:Chuan Chen 阅读数:33391人阅读 分类: JavaScript

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:

  1. Each currying operation creates a new function object, potentially increasing memory consumption.
  2. Deeply nested currying calls may increase the call stack.
  3. 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

上一篇:函数属性和方法

下一篇:对象创建方式

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.