Functional Programming (Part 3): The Power of Currying

Separation of Concerns Principle Using Currying!

Ahmad M. Hawwash
Bits and Pieces

--

Photo by Engin Akyurt at Pexels

This article is a part of a series that talks about “Functional Programming”

In the previous article in this series we discussed a core and fundamental part of any function in any functional language; Pure functions and its characteristics. In this article we’ll be talking about Currying.

Table of contents

  • What is Currying
  • How currying works
  • Why currying
  • Why currying makes our code better
  • Closure and Currying Relationship
  • More Examples
  • Conclusion

What is Currying

A curried function is a function that keeps returning functions until all its params are fulfilled

How Currying Works

Let’s say we have add function

const add = (a, b) => a + b

The simplest implementation of currying is to make a function return a function and so on, like:

const add = (a) => (b) => a + b

Where that can be used like this:

const addOne = add(1) // addOne = (b) => 1 + b

But let’s imagine that we have a curry function that takes the function and curry it. For example:

const add = curry((a, b) => a + b)

As we see, curry is a function that takes another function to lazi-fy the params. So now we can invoke it like this:

const addOne = add(1) // addOne = (b) => 1 + b

So, first we created addOne by passing 1 as a first param (a) to the curried add function. Which yielded another function that waits for the rest of the params, where the logic of add will not be executed until all params are provided.

addOne(2) // 3

Now, passing 2 (as b) to addOne; executes the logic 1 + 2

Quick conclusion:

curry function takes a function and makes its params lazy, in other words you provide these params as you need/go. Just like addOne

Quick note:

You still can call the curried version of add function like this:

const three = add(1, 2)

So it either takes the arguments piece by piece or all the arguments at once.

Why Currying

Currying will make our code:

  1. Cleaner
  2. Less repetitive params passing and less verbose code
  3. More composable
  4. More reusable

Why Currying Makes our Code Better

Mainly, some functions take “config” data as input

If we have functions that take “config” params, we better curry them because these “configs” will probably be repeated over and over again.

For example, let’s suppose we have a translator function, that takes a locale and a text to be translated:

const translator = (locale, text) => {/*translation*/}

The usage would look like this:

translator('fr', 'Hello')
translator('fr', 'Goodbye')
translator('fr', 'How are you?')

Every time we call translator we should provide locale and text. Which is redundant and dirty to provide the locale on every call.

But instead, let’s curry translator like this:

const translator = curry((locale, text) => {/*translation*/})const inFrench = translator('fr') 

Now inFrench has fr as locale provided to the curried translator function and waits for text to be provided. We can use it like this:

inFrench('Hello')
inFrench('Goodbye')
inFrench('How are you?')

Currying did us a great favour indeed, we don’t need to specify the locale each time, instead the curried inFrench has locale due to currying.

After currying -in this specific example. Code is:

  1. Cleaner
  2. Less verbose and less redundant

Because we separated “config” from actual “data”. Which is quite handy in many areas and use cases.

In real life

In practice we have dynamic locale (each user has a different language) might be fr, en, de or anything. So instead we better rename inFrench to translate, where translate can be loaded with any locale.

Now we have translator that takes a locale as “config” and text as data. Due to the fact that translator is curried, we were able to separate “config” from “data” params.

Why separate “config” from “data” params?

Many components and functions need the use of some functionalities (translate in our case) but shouldn’t or can’t know about the “config” part (locale). Where these components or functions have the “data” only part (text). So these functions will be able to use that function without the need of knowing about the “config” part.

Thus, that component or function will be less coupled with the system, which will make the components more composable and more maintainable.

When do we apply this idea

When we know that there is “config” and there is “data” in a function, we better curry it.

Currying will give us the ability to separate them. And that is a sign of a mature system design. Because one of the large pillars of code qualities is separation of concerns.

Even if a function needs all the params to operate well, we still know better when to pass the params and on which layer of that app.

Closure and Currying Relationship

A Closure: is a function returned by a “parent” function and has access to the parent function’s internal state. (described earlier here)

Currying: will always result a closure. Because each function returned by a curried function will be provided with parents’ internal state.

More Examples

Before we dive deeper

Let’s introduce some utilities, so we can have a deeper look.

Array prototype has utilities like filter, map and others. But they are not curry-able, because they use dot (.) notation.

So let’s convert them to curry-able format:

const filter     = (fn, list)   => list.filter(fn)
const map = (fn, list) => list.map(fn)
const startsWith = (starter, s) => s.startsWith(starter)

Now we can use them like this:

const lessThan18 = user => user.age < 18// Converting this format
const filteredUsers = users.filter(lessThan18)
// To this format instead
const filteredUsers = filter(lessThan18, users)

(We eliminated the dot notation, and passed processed data as a last param)

Then we curry them. Where this curry function will take a function and return a curried function (you can find the implementation here):

const filter     = curry((fn, list)   => list.filter(fn))
const map = curry((fn, list) => list.map(fn))
const startsWith = curry((starter, s) => s.startsWith(starter))

Examples

Now we can do some meaningful examples…

Example 1️:

Given a list of numbers, increment all numbers by 1

Input: [1, 2, 3, 4, 5]

Output: [2, 3, 4, 5, 6]

Solution:

// the curried `add` function that we defined earlier
const
addOne = add(1)
const incrementNumbers = map(addOne)const incrementedNumbers = incrementNumbers(numbers)

Example 2️:

Given a string, keep all words that start with ‘C’ letter

Input: "currying is awesome”

Output: “currying”

Solution:

const startsWithC = startsWith('c')const filterStartsWithC = filter(startsWithC)const filteredWords = filterStartsWithC(words)

Example 3️:

Given a list of ranges and a list of numbers; Create an array of functions that can filter numbers based on provided ranges.

Input:

const ranges = [
{min: 10, max: 100},
{min: 100, max: 500},
{min: 500, max: 999}
]
const numbers = [30, 50, 110, 200, 650, 700, 1000]// 30 & 50 within 1st range
// 110 & 200 within 2nd range
// 650 & 700 within 3rd range
// 1000 isn't in any range

Output: an array of functions; Each function can take numbers and return filtered numbers that are within the given range.

Solution:

const isInRange = curry(
(range, val) => val > range.min && val < range.max
)
const filters = ranges.map((range) => filter(isInRange(range)))

This example has double curry if you can spot it, the filter and the isInRange

filters now is a list of functions, each is waiting for numbers to process, loaded (“config”-ed) with min and max

Explanation:

The best explanation would be to unfold the currying, and use regular functions instead…

const isInRange = (range, val) => val > range.min && val < range.maxconst filters = ranges.map(
(range) => (numbers) => numbers.filter(
number => isInRange(range, number)
)
)

Remember, () => () => ... is still another implementation of currying. The simple version of currying.

And all thanks to currying! ❤️

Conclusion

Currying is just about making the params lazy. Where the function keeps returning function until all of its arguments are fulfilled then it computes and returns the result.

We also saw how it makes our code cleaner, less verbose, more composable and even more reusable through practical examples. And that leveraged separation of concerns principle.

Thanks a lot for taking time reading through this article ❤️ I’m cooking the next ones in the series. Please let me know what you think in the comments about this article or the series.

This is an article in a series of articles talking about Functional Programming

In this series of articles we cover the main concepts of Functional Programming. At the end of the series, you’ll be able to tackle problems in a more functional approach.

This series discusses:

0. A Brief Comparison Between Programming Paradigms

  1. First Class functions
  2. Pure functions
  3. Currying (this article)
  4. Composition
  5. Functors
  6. Monads

--

--

twitter: @AhmadMHawwash. Frontend engineering consultant @Mirado Stockholm. With interest in JS, TS, FP, React, Nextjs, Clean Code, Clean Architecture…