DEV Community

TK
TK

Posted on • Updated on • Originally published at leandrotk.github.io

Playing around with Closures, Currying, and Cool Abstractions

A Japanese curry rice


This article was first published at the TK's blog.

In this article, we will talk about closures, curried functions, and play around with these concepts to build cool abstractions. I want to show the idea behind each concept, but also make it very practical with examples and refactor code to make it more fun.

Closures

So closure is a common topic in JavaScript and we will start with it. As MDN web docs defines:

"A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment)."

Basically, every time a function is created, a closure is also created and it gives access to all state (variables, constants, functions, etc). The surrounding state is known as the lexical environment.

Let's show a simple example:

function makeFunction() {
  const name = 'TK';
  function displayName() {
    console.log(name);
  }
  return displayName;
};

What do we have here?

  • Our main function called makeFunction
  • A constant named name assigned with a string 'TK'
  • The definition of the displayName function (that just log the name constant)
  • And finally the makeFunction returns the displayName function

This is just a definition of a function. When we call the makeFunction, it will create everything within it: constant and function in this case.

As we know, when the displayName function is created, the closure is also created and it makes the function aware of the environment, in this case, the name constant. This is why we can console.log the name without breaking anything. The function knows about the lexical environment.

const myFunction = makeFunction();
myFunction(); // TK

Great! It works as expected! The return of the makeFunction is a function that we store it in the myFunction constant, call it later, and displays TK.

We can also make it work as an arrow function:

const makeFunction = () => {
  const name = 'TK';
  return () => console.log(name);
};

But what if we want to pass the name and display it? A parameter!

const makeFunction = (name = 'TK') => {
  return () => console.log(name);
};

// Or a one-liner
const makeFunction = (name = 'TK') => () => console.log(name);

Now we can play with the name:

const myFunction = makeFunction();
myFunction(); // TK

const myFunction = makeFunction('Dan');
myFunction(); // Dan

Our myFunction is aware of the arguments passed: default or dynamic value.
The closure does make the created function not only aware of constants/variables, but also other functions within the function.

So this also works:

const makeFunction = (name = 'TK') => {
  const display = () => console.log(name);
  return () => display();
};

const myFunction = makeFunction();
myFunction(); // TK

The returned function knows about the display function and it is able to call it.

One powerful technique is to use closures to build "private" functions and variables.

Months ago I was learning data structures (again!) and wanted to implement each one. But I was always using the object oriented approach. As a functional programming enthusiast, I wanted to build all the data structures following FP principles (pure functions, immutability, referential transparency, etc).

The first data structure I was learning was the Stack. It is pretty simple. The main API is:

  • push: add an item to the first place of the stack
  • pop: remove the first item from the stack
  • peek: get the first item from the stack
  • isEmpty: verify if the stack is empty
  • size: get the number of items the stack has

We could clearly create a simple function to each "method" and pass the stack data to it. It use/transform the data and return it.

But we can also create a private stack data and exposes only the API methods. Let's do this!

const buildStack = () => {
  let items = [];

  const push = (item) => items = [item, ...items];
  const pop = () => items = items.slice(1);
  const peek = () => items[0];
  const isEmpty = () => !items.length;
  const size = () => items.length;

  return {
    push,
    pop,
    peek,
    isEmpty,
    size,
  };
};

As we created the items stack data inside our buildStack function, it is "private". It can be accessed only within the function. In this case, only the push, pop, etc could touch the data. And this is what we're looking for.

And how do we use it? Like this:

const stack = buildStack();

stack.isEmpty(); // true

stack.push(1); // [1]
stack.push(2); // [2, 1]
stack.push(3); // [3, 2, 1]
stack.push(4); // [4, 3, 2, 1]
stack.push(5); // [5, 4, 3, 2, 1]

stack.peek(); // 5
stack.size(); // 5
stack.isEmpty(); // false

stack.pop(); // [4, 3, 2, 1]
stack.pop(); // [3, 2, 1]
stack.pop(); // [2, 1]
stack.pop(); // [1]

stack.isEmpty(); // false
stack.peek(); // 1
stack.pop(); // []
stack.isEmpty(); // true
stack.size(); // 0

So, when the stack is created, all the functions are aware of the items data. But outside the function, we can't access this data. It's private. We just modify the data by using the stack builtin API.

Curry

"Currying is the process of taking a function with multiple arguments and turning it into a sequence of functions each with only a single argument." - Wikipedia

So imagine you have a function with multiple arguments: f(a, b, c). Using currying, we achieve a function f(a) that returns a function g(b) the returns a function h(c).

Basically: f(a, b, c) —> f(a) => g(b) => h(c)

Let's build a simple example: add two numbers. But first, without currying!

const add = (x, y) => x + y;
add(1, 2); // 3

Great! Super simple! Here we have a function with two arguments. To transform it into a curried function we need a function that receives x and returns a function that receives y and returns the sum of both values.

const add = (x) => {
  function addY(y) {
    return x + y;
  }

  return addY;
};

We can refactor this addY into a anonymous arrow function:

const add = (x) => {
  return (y) => {
    return x + y;
  }
};

Or simplify it by building one liner arrow functions:

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

These three different curried functions have the same behavior: build a sequence of functions with only one argument.

How we use it?

add(10)(20); // 30

At first, it can look a bit strange, but it has a logic behind it. add(10) returns a function. And we call this function with the 20 value.

This is the same as:

const addTen = add(10);
addTen(20); // 30

And this is interesting. We can generate specialized functions by calling the first function. Imagine we want an increment function. We can generate it from our add function by passing the 1 as the value.

const increment = add(1);
increment(9); // 10

When I was implementing the Lazy Cypress, a npm library to record the user behavior in a form page and generate Cypress testing code, I want to build a function to generate this string input[data-testid="123"]. So here we have the element (input), the attribute (data-testid), and the value (123). Interpolating this string in JavaScript would look like this: ${element}[${attribute}="${value}"].

the first implementation in mind is to receive these three values as parameters and return the interpolated string above.

const buildSelector = (element, attribute, value) =>
  `${element}[${attribute}="${value}"]`;

buildSelector('input', 'data-testid', 123); // input[data-testid="123"]

And it is great. I achieved what I was looking for. But at the same time, I wanted to build a more idiomatic function. Something I could write "get an element X with attribute Y and value Z". So what if we break this phrase into three steps:

  • "get an element X": get(x)
  • "with attribute Y": withAttribute(y)
  • "and value Z": andValue(z)

We can transform the buildSelector(x, y, z) into get(x)withAttribute(y)andValue(z) by using the currying concept.

const get = (element) => {
  return {
    withAttribute: (attribute) => {
      return {
        andValue: (value) => `${element}[${attribute}="${value}"]`,
      }
    }
  };
};

Here we use a different idea: returning an object with function as key-value. This way we can achieve this syntax: get(x).withAttribute(y).andValue(z).

And for each returned object, we have the next function and argument.

Refactoring time! Remove the return statements:

const get = (element) => ({
  withAttribute: (attribute) => ({
    andValue: (value) => `${element}[${attribute}="${value}"]`,
  }),
});

I think it looks prettier. And we use it like:

const selector = get('input')
  .withAttribute('data-testid')
  .andValue(123);

selector; // input[data-testid="123"]

The andValue function knows about the element and attribute values because it is aware of the lexical environment as we talked about closures before.


We can also implement functions using "partial currying". Separate only the first argument from the rest for example.

Doing web development for a long time, I commonly used the event listener Web API. It is used this way:

const log = () => console.log('clicked');
button.addEventListener('click', log);

I wanted to create an abstraction to build specialized event listeners and use them by passing the element and callback handler.

const buildEventListener = (event) => (element, handler) => element.addEventListener(event, handler);

This way I can create different specialized event listeners and use it as functions.

const onClick = buildEventListener('click');
onClick(button, log);

const onHover = buildEventListener('hover');
onHover(link, log);

With all these concepts, I could create a SQL query using JavaScript syntax. I wanted to SQL query a JSON data like:

const json = {
  "users": [
    {
      "id": 1,
      "name": "TK",
      "age": 25,
      "email": "tk@mail.com"
    },
    {
      "id": 2,
      "name": "Kaio",
      "age": 11,
      "email": "kaio@mail.com"
    },
    {
      "id": 3,
      "name": "Daniel",
      "age": 28,
      "email": "dani@mail.com"
    }
  ]
}

So I built a simple engine to handle this implementation:

const startEngine = (json) => (attributes) => ({ from: from(json, attributes) });

const buildAttributes = (node) => (acc, attribute) => ({ ...acc, [attribute]: node[attribute] });

const executeQuery = (attributes, attribute, value) => (resultList, node) =>
  node[attribute] === value
    ? [...resultList, attributes.reduce(buildAttributes(node), {})]
    : resultList;

const where = (json, attributes) => (attribute, value) =>
  json
    .reduce(executeQuery(attributes, attribute, value), []);

const from = (json, attributes) => (node) => ({ where: where(json[node], attributes) });

With this implementation, we can start the engine with the JSON data:

const select = startEngine(json);

And use it like a SQL query:

select(['id', 'name'])
  .from('users')
  .where('id', 1);

result; // [{ id: 1, name: 'TK' }]

That's it for today. We could go on and on showing a lot of different examples of abstractions, but now I let you play with those concepts.

Resources

Top comments (0)