We're planting a tree for every job application! Click here to learn more

JavaScript And Functional Programming Part 2: Composition & Currying

ketan srivastav

13 Jun 2022

3 min read

JavaScript And Functional Programming Part 2: Composition & Currying
  • Ramda

We covered how JavaScript’s map() and filter() methods work in Part 1(JavaScript And Functional Programming Part 1: Map & Filter). Now we will look at another staple of functional programming: Composition and Currying

We will be using RamdaJS to curry and compose our functions. If you want to play around with the examples from this article on DevTools console, use the following snippet to load ramdajs:

var s = document.createElement(“SCRIPT”);
s.src = “https://cdn.jsdelivr.net/npm/ramda@0.24.1/dist/ramda.min.js";
document.head.appendChild(s);

Let’s start with functional composition:

Functional composition is the act of combining multiple functions to form a pipeline where the result of each function is passed as the argument of the next, and the result of the last one is the result of the whole.

We will be using R.compose to compose our pipeline. A couple of things to remember about R.compose() before we dive into an example:

The pipeline flows from right to left. The function on the rightmost side is called first and then its return value is passed to the function on its left. The output of the last function will be the result of the composition.

Only the first function can be of multiple arities, the rest of the functions can only take one argument. Consider the following data set:

const data = [
 {
 “name”: “Avengers: Endgame”,
 “year”: “2019”,
 “genre”: “action”
 },
 {
 “name”: “Titanic”,
 “year”: “1997”,
 “genre”: “Romance”
 },
 {
 “name”: “Skyfall”,
 “year”: “2012”,
 “genre”: “action”
 },
 {
 “name”: “The Dark Knight”,
 “year”: “2008”,
 “genre”: “action”
 },
 {
 “name”: “Joker”,
 “year”: “2019”,
 “genre”: “action”
 },
 {
 “name”: “Star Wars: The Rise of Skywalker “,
 “year”: “2019”,
 “genre”: “action”
 }
]

Let’s say we want to first filter movies by the year “2019” and then by the “action” genre.

We can compose the above operation like this:

const getActionMoviesFrom2019 = R.compose(R.filter(m=>m.genre == "action"),R.filter(m=>m.year== "2019"))
getActionMoviesFrom2019(data)

We define a composition called “getActionMoviesFrom2019" and call it in the next statement with our dataset.

Moving from right to left, we first filter our data to only include movies from “2019”. The resulting array is passed to the next filter function which further selects movies of the “action” genre, which is the output of our pipeline.

Composition in immensely useful when you’re dealing with a big list that needs a chunk of operations performed to it one after another, often one operation further refining the dataset for the upcoming functions in the pipeline.

Alright, let's move onto Currying

Currying happens when you call a function of n arity with less than n arguments and you get a partially applied function back For example:

function add(num1, num2) {
return num1 + num2
}

Let’s say we only have “num1” with us at the moment. Let’s apply “num1" on the function “add” while we wait for “num2” to become available:

const addCurried = R.curry(add); This will give back a function which will take 2 arguments.

If we were to call addCurried with 2 arguments it will return the sum of those two numbers.

However, if we call addCurried with only 1 argument it will return a function which will have partially applied the given argument:

const addWith4 = addCurried(4)

Now when we apply the second argument to “addWith4” like:

const result = addWith4(5)

It will treat the partially applied 4 as “num1” and the now provided 5 as “num2” in the originally curried function “add()” resulting in the addition of the two given arguments

When is currying useful? When you’re operating on data which is partly constant and partly variable. For instance, consider the above-mentioned movie dataset. What if it has hundreds of thousands of records. For example, you know that year “2019” will remain constant for your queries but the genre might change. In that case , you can simple curry the filter method with the year “2019” partially applied like this:

// define a curried function which takes partial values dataset, year, and genre
const getMoviesByGenreForYear = R.curry((data, year, genre) => {
  const filteredListByYear = data.filter(m => m.year == year)
  const filteredListByGenre = filteredListByYear.filter(m => m.genre == genre)
  return filteredListByYear
});

To go about using and partially applying the arguments, we apply the dataset to our function first:

const getMoviesByGenreForYearWithData =  getMoviesByGenreForYear(data)

Then, we apply the year “2019” to the function “getMoviesByGenreForYear()”

getMoviesFromYear2019For = getMoviesByGenreForYearWithData (“2019”)

Now, we can query the function “getMoviesForYear2019For()" for different genres, such as:

getMoviesFromYear2019For("action")
getMoviesFromYear2019For("drama") 
getMoviesFromYear2019For("comedy")

Let’s review how we achieved this.

We first curried a function that can take up to 3 arguments — dataset, the year a movie got released, and its genre. Then we provided it with the dataset (first argument). That gave us another function which has the dataset applied to it and is now waiting for 2 more arguments. Since we are interested in movies from 2019 we applied that next and got back the function “getMoviesForYear2019()”. This function now has two of our arguments applied. It just needs the “genre” argument and as soon as we call the function with the genre argument the entire function a runs with all 3 arguments applied and returns the list we want.

Currying and compose often work hand in hand since composed functions can only take one argument — you need to partially apply multiple arguments to the functions of a compositional pipeline.

With composition and currying in your arsenal, you’re looking at a more efficient and readable code. What's more, is that once you start tackling your problems from more of a functional programming mindset the efficiency of your algorithms improves dramatically. That has certainly been the case with me ever since I started programming in Clojure — which is a functional LISP language for the JVM.

Did you like this article?

ketan srivastav

See other articles by ketan

Related jobs

Title

The company

title

Remote

Title

The company

title

Remote

Title

The company

title

Remote

Title

The company

title

Remote

Related articles

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

12 Sep 2021

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

12 Sep 2021

CareersCompaniesSitemapFunctional WorksBlockchain WorksJavaScript WorksAI WorksGolang WorksJava WorksPython WorksRemote Works
email iconhello@works-hub.comUK flag

Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ

US flag

108 E 16th Street, New York, NY 10003

Subscribe to our newsletter

Join over 111,000 others and get access to exclusive content, job opportunities and more!

© 2021 WorksHub

Privacy PolicyDeveloped by WorksHub