DEV Community

Cover image for When to use currying in JavaScript
SlimTim10
SlimTim10

Posted on

When to use currying in JavaScript

This post is about the concept of currying, from functional programming, and when you should use it in JavaScript.

I'm going to be honest. You probably don't need to use currying in JavaScript. In fact, trying to fit it in your code is going to do more harm than good, unless it's just for fun. Currying only becomes useful when you fully embrace functional programming, which, in JavaScript, means using a library like Ramda instead of the standard built-in functions.

In this article, I'm going to start by explaining what currying is, then show how it can be useful in a functional programming context.


What is currying?

Currying is the concept that functions never need to take multiple arguments; every function only takes a single argument. If a function needs to behave as if it takes multiple arguments, it returns another function instead.

A regular, non-curried function is what you're used to seeing:

const add = (x, y) => x + y

console.log(
  add(2, 3) // 2 + 3
) // prints 5
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

This is a simple function that takes two numbers and returns their sum.

A curried version of the same function looks like this:

const addCurried = x => y => x + y

console.log(
  addCurried(2)(3) // 2 + 3
) // prints 5
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Instead of the function taking two arguments, it takes one argument, then returns another function that takes one argument and returns the sum. Notice how we have to pass the arguments to the function using more brackets, since the arguments are passed one at a time to each nested function.

We can have as many arguments as we want this way:

const addMore = a => b => c => d => e => a + b + c + d + e

console.log(
  addMore(1)(2)(3)(4)(5) // 1 + 2 + 3 + 4 + 5
) // prints 15
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Because of the way a curried function works, we can do something called partial application. This is when we give a function fewer arguments than it can take:

const addCurried = x => y => x + y

console.log(
  addCurried(2) // y => 2 + y
) // [Function (anonymous)]
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

If we only pass addCurried one argument, the result is a function that expects another argument. In other words, the 2 went into the x argument, so we're left with y => 2 + y. If we want, we can store this partially applied function into a variable so we can use it after the fact:

const addCurried = x => y => x + y
const add2 = addCurried(2)
// This is the same as:
// const add2 = y => 2 + y

console.log(
  add2(3) // 2 + 3
) // prints 5

console.log(
  add2(10) // 2 + 10
) // prints 12
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Now we have a function add2, which is expecting a single argument. Whatever we give it, it will add 2 to it.


When is currying useful?

Like I said, in a typical JavaScript codebase, it's not. You can probably tell that the addCurried example is very contrived and doesn't demonstrate any real benefit. But if you want to go deeper down the functional programming rabbit hole, let me show you how using curried functions can be even more elegant than the typical practice.

It's all about composition.

In functional programming, composing functions is a fundamental concept. This means using one function after another on some data. It's in using composition that curried functions really shine.

In JavaScript, the way to compose two functions looks like this:

const compose = (f, g) => x => f(g(x))

const addCurried = x => y => x + y

console.log(
  compose(addCurried(2), addCurried(3))(10) // 10 + 3 + 2 = 15
) // prints 15
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

When using composition, you should read it as operations being done on some data from right to left. In the above example, the starting data is 10, which goes through adding 3, followed by adding 2, resulting in 15.

Let me show by example how composing a series of curried functions looks when compared to idiomatic functional JavaScript. I'm going to use an example based on a real problem I had to solve that doesn't call for any particular programming style or language.

The objective is to make a function cleanExpression that takes in a basic math expression as a string (e.g., "1 + 10 / 2") and returns a cleaned version of it. The cleaning process is to remove extra spaces and make sure the expression alternates numbers and operators (there should never be two numbers or two operators next to each other). We're dealing with single-digit numbers only.

For example, "1    + 2 2 / 3 *" cleaned would be "1 + 2 / 3".

The following is an idiomatic functional JavaScript solution. Let's call this "functional-lite".

// Helper functions
const isOperator = x => "+-*/".includes(x)

const isDigit = x => "1234567890".includes(x)

const last = xs => xs[xs.length - 1]

const init = xs => xs.slice(0, -1)

const intersperse = (sep, xs) => xs.map(x => [sep, x]).flat()

// The main function
const cleanExpression = expr => {
  const parseNext = ([acc, shouldBe], x) => {
    if (shouldBe === 'digit' && isDigit(x)) {
      return [[...acc, x], 'operator']
    } else if (shouldBe === 'operator' && isOperator(x)) {
      return [[...acc, x], 'digit']
    } else {
      return [acc, shouldBe]
    }
  }

  const chars = expr.split('')
  const alternating = chars.reduce(parseNext, ['', 'digit'])[0]
  const cleaned = isOperator(last(alternating)) ? init(alternating) : alternating
  return intersperse(' ', cleaned).join('')
}

console.log(
  cleanExpression('1    + 2 2 / 3 *')
)
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

And here is a more functional JavaScript solution using the Ramda library in-place of built-in functions:

const R = require('ramda')

const isOperator = x => R.includes(x, "+-*/")

const isDigit = x => R.includes(x, "1234567890")

const cleanExpression = expr => {
  const parseNext = ([acc, shouldBe], x) => {
    if (shouldBe === 'digit' && isDigit(x)) {
      return [acc + x, 'operator']
    } else if (shouldBe === 'operator' && isOperator(x)) {
      return [acc + x, 'digit']
    } else {
      return [acc, shouldBe]
    }
  }

  return R.compose(
    R.join(''),
    R.intersperse(' '),
    (xs => isOperator(R.last(xs)) ? R.init(xs) : xs),
    R.head,
    R.reduce(parseNext, ['', 'digit']),
    R.split('')
  )(expr)
}

console.log(
  cleanExpression('1    + 2 2 / 3 *')
)
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Fewer helper functions are needed because Ramda implements the others, but that's not what's important. The main lines to compare are in the body of cleanExpression:

// functional-lite
const chars = expr.split('')
const alternating = chars.reduce(parseNext, ['', 'digit'])[0]
const cleaned = isOperator(last(alternating)) ? init(alternating) : alternating
return intersperse(' ', cleaned).join('')

// more functional
return R.compose(
  R.join(''),
  R.intersperse(' '),
  (xs => isOperator(R.last(xs)) ? R.init(xs) : xs),
  R.head,
  R.reduce(parseNext, ['', 'digit']),
  R.split('')
)(expr)
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Ramda's compose function extends function composition to any number of functions instead of only two. Still, it should be read from right to left (or bottom to top). The above example can be understood as:

  • Feed in expr as the data to be operated on, which in this case should be a math expression as a string.
  • Split the string, turning it into an array of characters.
  • Use reduce to walk through the expression, building a new version that alternates digits and operators (beginning with a digit).
  • Take the first element of the previous result (because it returned a pair and we only need the new expression).
  • Remove the last character if it is an operator.
  • Intersperse the new expression with spaces.
  • Finally, Convert the new expression into a string.

This way, we can think of the solution as processing some data through a pipeline (bottom to top). The output of each step feeds into the input of the next one until we reach the end, which gets returned as a final result.

The steps of the solution are the same in both versions, but the second version looks more linear and we can clearly see each step.

For the most functional version, here's the same solution in Haskell, where all functions are curried by default and the composition operator is a dot (.):

isOperator :: Char -> Bool
isOperator x = x `elem` "+-*/"

isDigit :: Char -> Bool
isDigit x = x `elem` "1234567890"

intersperse :: Char -> String -> String
intersperse sep = init . concat . map (\x -> [x, sep])

cleanExpression :: String -> String
cleanExpression =
  intersperse ' '
  . (\xs -> if isOperator (last xs) then init xs else xs)
  . fst
  . foldl parseNext ("", "digit")
  where
    parseNext :: (String, String) -> Char -> (String, String)
    parseNext (acc, shouldBe) x
      | shouldBe == "digit" && isDigit x =
        (acc ++ [x], "operator")
      | shouldBe == "operator" && isOperator x =
        (acc ++ [x], "digit")
      | otherwise = (acc, shouldBe)

main :: IO ()
main = do
  print $ cleanExpression "1    + 2 2 / 3 *" == "1 + 2 / 3"
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Conclusion

Currying is not a very complicated concept, but most people are unfamiliar with it because they have no use for it. And for good reason! It only shines when you decide to write very functional code and use composition everywhere. Languages like Haskell take advantage of this by defining all functions to be curried by default and having a very small operator for composing functions (like a dot).

For a fun exercise, try implementing Ramda's compose function on your own! It should be able to compose any number of functions, not just two.


Originally published at https://timjohns.ca.

Top comments (16)

ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
pengeszikra profile image
Peter Vivo • • Edited

If you add pipeline operators to build, then currying will be much easier because you much cleaner way compose functions together.

userInputEvent 
  |> normalizeEventParameters 
  |> validateInputState 
  |> render
;
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

which is equal:

render(validateInputState(normalizeEventParameters(userInputEvent)));

just fare more readable.

My luck, for 2 years I can write js fn code with pipeline operator.

ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
joelbonetr profile image
JoelBonetR ðŸĨ‡ •

Isn't this an experimental feature in stage 2?

Afaik tou need babel or another tool to "patch" it into a different expression for it to work.

ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
pengeszikra profile image
Peter Vivo • • Edited

You're right, the pipeline operator is just a Stage 2 experimental feature. The science application written in React is quite complex, and Babel is also included in the build system.

.babelrc

{
  "presets": [
    "@babel/preset-react",
    "@babel/preset-typescript"
  ],
  "plugins": [
    [ "@babel/plugin-proposal-pipeline-operator", {"proposal": "minimal"}],
    [ "@babel/plugin-transform-runtime", { "regenerator": true }],
    [ "auto-import", { "declarations": [{ "default": "React", "path": "react" }]}]
  ]
}
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
pengeszikra profile image
Peter Vivo •

in VS code, also need to be set:

"javascript.validate.enable": false

ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” Thread
 
joelbonetr profile image
JoelBonetR ðŸĨ‡ • • Edited

Disabling all built-in syntax checking with "javascript.validate.enable": false seems risky.

You'll need to set up a tone of rules in ESLint to validate the rest, all this just to use a single feature that needs to escalate 2 stages more before being implemented.

I'd wholeheartedly suggest avoid using the pipeline operator till it's implemented in the core API (if it does end up like that). In the meantime, you can have a function to handle that pretty easily:

/** Executes Async functions in the order they are passed to, 
sending the input argument as argument for the first function */
export const pipeAsync: any =
  (...fns: Promise<Function>[]) =>
  (initialArg: any) =>
    fns.reduce((currentFunc: Promise<Function>, func: Promise<Function> | any) => 
      currentFunc.then(func), Promise.resolve(initialArg));
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

It will work for both async and sync operations, though you can have a sync version of it as well, like so:

/** It executes functions in the order they are passed to, 
sending the input argument as argument for the first function */
export const pipe =
  (...functions: Function[]) =>
  (initialArg: any) =>
    functions.reduce((chain, func) => 
      func(chain), initialArg);
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Usage example:

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  switch (req.method) {
    case 'GET':
      return pipeAsync(provide, parseData, mock, answer)(req.headers).catch((error: Error) => console.error(error));
    default:
      return res.status(405).end(`Method ${req.method} Not Allowed`);
}
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

You can learn more about this here:

Best regards 😃

ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” Thread
 
cemkaan profile image
CEM KAAN KÖSALI •

or just ust ramda.js

ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” Thread
 
joelbonetr profile image
JoelBonetR ðŸĨ‡ • • Edited

Function Composition/Piping and Currying are not the same, tho

ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
webduvet profile image
webduvet •

IMO not necessary to add to language core. It easily can be achieved with transpilers or with library like ramda using traditional syntax.

pipe(fn1, fn2, fn3)(input)
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
pengeszikra profile image
Peter Vivo •

But ramada give extra weigth to program vs pipelin operator transformed to pure js function call.

ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” Thread
 
webduvet profile image
webduvet •

even ramda gives you pure function calls. It's down to optimization. I doubt parsing pipe code through babel would give you any performance boost. But who knows i haven't tried. I have nothing against pipe syntax. If it makes it into specification I will use it.

ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
nevodavid profile image
Nevo David • • Edited

it's actually very usuful in React...
Here is an example

const doSomething = useCallback((index: number) => () => {
...do something
});
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

And later:

somevariable.map((_, index) => <div onClick={doSomething(index)} />
ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

I use this concept a lot.

ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
tracygjg profile image
Tracy Gilmore •

Hi SlimTim, In the last few years I have been learning more FP techniques and applying them where appropriate in my JS code. Currying is an important technique to know but I have yet to find an opportunity to employ it. However, I use the associated technique of Partial Application (AP) extensively.
This is a fine discussion of the topic of Currying and I hope you follow it up with one on PA, which I think has far more utility.
Regards, Tracy

ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
ravavyr profile image
Ravavyr •

currying is terrible, pass multiple parameters to your functions, it's literally gonna save you time, money, and your sanity. functions should not return other functions; they should return values, at least in web development.

ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
caixiangyangcd profile image
CaixiangyangCD •

great

ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
sergeyleschev profile image
Sergey Leschev •

Currying is not commonly needed in typical JavaScript codebases. However, where function composition is fundamental, curried functions can be more elegant than the normal practice.

ðŸ”ī Hot News: slimtim/when to use currying in javascript cn - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”
 
fruntend profile image
fruntend •

ÐĄongratulations ðŸĨģ! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up 👍