Curry Functions in JavaScript

Curry Functions in JavaScript

What is it?

Curry functions are not asian spiced functions. I do love curry chicken and the lot, but, alas, that is not what we are talking about here.

Currying a function sounds complex, but in practice it is quite straightforward. Start by writing a basic JavaScript function (using ES6 syntax);

const addThree = (number) => {
    return a + 3;
};

addThree(4);   // => 7

What if we wanted a function that added four, not three? Maybe we create another function addFour and do something similar.

const addFour = (number) => {
    return a + 4;
};

addFour(5);   // => 9

However, we are software developers; simplicity, readability, and "working smarter not harder" should always be a goal. We aren't software engineers for nothing. Our very job is to automate and solve problems using software.

Here is where curried functions fit in. In one function, you can build two functions that can addFour AND addThree. Here's how.

const add = (amount) => (number) => {
    return number + amount;
};

const addThree = add(3);
const addFour = add(4);

addThree(3); // => 6
addFour(8); // => 12

For those of you familiar with arrow function notation, this may look funky but you probably get it. It's purely a function returning... another function. If it helps, here is another way to write this curried add function:

const add = (amount) => {
    return (number) => {
        return number + amount;
    }
};

Do you see the abstraction? The possibilities? Now if you wanted more functions, maybe addFive or addSix, you wouldn't need to create new functions, you could simple do:

const addFive = add(5);
const addSix = add(6);

You might be wondering why you wouldn't just do const add = (amount, number) => .... Valid question.

Currying allows for partial application. A partial application is a function which has been applied to some, but not yet all of its arguments.

Use case

I recently build a complaint forum application to try out a new tech stack I was interested in: Svelte + Tailwind + Airtable (as DB) + AWS S3 (purely static).

I used Airtable as my db and backend (using their API). I created a wrapper function in order to interact with my different tables: complaints, users, comments, and threads.

const baseUrl = "https://api.airtable.com/v0/<airtable-project-key>"

const createFetcher = table => async ({
  method = 'GET',
  params = '',
  id = '',
  data = {}
} = {}) => {
  const extras = {
    headers: {
      'Authorization': 'Bearer <airtable-api-key>',
      'Content-Type': 'application/json',
    },
    method,
  };

  if (method !== 'GET') {
    extras.body = JSON.stringify(data)
  }

  if (!!params) {
    params = Object.keys(params).map(function(key) {
      return key + '=' + params[key];
    }).join('&');
  }

  const res = await fetch(baseUrl + `/${table}${id ? '/' + id : ''}?` + params, extras)

  return res.json()
}

export const Complaints = createFetcher('Complaints')
export const Comments = createFetcher('Comments')
export const Threads = createFetcher('Threads')
export const Users = createFetcher('Users')

Breakdown

the createFetcher is a curried function that allows you to create a wrapper around each table without having to do all the fetch boilerplate work. Then at the bottom I create a fetcher function for each table.

Usage

To delete a thread.. or comment.

Threads({
    method: 'DELETE',
    id: threadId
});

Comments({
    method: 'DELETE',
    id: commentId
});

And there you have it; need to delete a Complaint? You know what to do.

Till next time, Cheers.