Skip to main content

A closer look at JavaScript closures, higher-order functions, and currying

JavaScript features

Closures

Closures are one of the most powerful JavaScript features, but they can be a little daunting at first. Having a solid understanding of closures paves the way for understanding topics like higher-order functions and currying.
We’re going to address a few concepts that help illustrate the principles of closures, higher-order functions, and currying.
Functions in JavaScript are first-class citizens, which means that:
  1. Functions can be assigned to variables
  2. Functions can be passed as arguments to other functions
  3. Functions can return other functions
// functions can be assigned to variables
    const morningGreetings = (name) => {
      console.log(`Good morning ${name}`); 
    }
    const eveningGreeting = function (name) {
      console.log(`Good evening ${name}`);
    }


    // functions can be passed as arguments to other functions
    const todaysGreeting = (morningGreetings, eveningGreeting) => {
      morningGreetings('Barack')
      console.log(`Thanks for all you have done during the day`);
      eveningGreeting('Barack');
    }


    // functions can return other functions
     function myCounter ()  {
      let count = 0
      return function () {
         return ++count;
      }
    }
    const noOfTimes = myCounter();
    console.log(noOfTimes()); // 1
The feature we’ll look closely at allows functions to return functions. The feature’s closure depends upon the unique characteristics of JavaScript.
In JavaScript, functions have the ability to reference a variable that isn’t defined in the function but is available within an enclosing function or the global scope.
Consider the following example:
const iamglobal = 'available throughout the programme';
    function funky() {
      const iamlocal = 'local to the function scope funky';
    }
    console.log(iamglobal);// available throughout the programme
    console.log(iamlocal); // iamlocal is not defined
As you can see, we’re unable to access the variable iamlocaloutside of the scope of the function funky. This is because the variable is only kept “alive” while the funky is active.
Once the function has been invoked, references to any variables declared within its scope are removed and the memory is handed back to the computer for use.
However, there’s a way we can have access to the variable declared within a function even after the function has been invoked.
This is where closures come in.
A closure is a reference to a variable declared in the scope of another function that is kept alive by returning a new function from the invocation of the existing function.
Let’s take a look at an example:
function outerScope() {
  const outside = 'i am outside';
  function innerScope() {
    const inside = 'i am inside';
    console.log('innerScope ➡️', outside);
    console.log('innerScope ➡️',inside);
  }
  console.log('outerScope ➡️', outside);
  innerScope();
}
outerScope();
// outerScope ➡️ i am outside
// innerScope ➡️ i am outside
// innerScope ➡️ i am inside
It’s possible to access the value of the variable outside from function innerScope. The concept of closures hinges on this capability.
From the example above, it’s possible for us to return the function innerScope rather than call it within the outerScope, since this is close to a real world scenario.
Let’s modify the example above to reflect this change:
function outerScope() {
  const outside = 'i am outside';
  function innerScope() {
    const inside = 'i am inside';
    console.log('innerScope ➡', outside);
    console.log('innerScope ➡',inside);
  }
  return innerScope
}

const inner = outerScope();
inner();
// outerScope ➡️ i am outside
// innerScope ➡️ i am outside
This resembles the example above, which illustrates how functions have the ability to return functions.
Let’s take this a step further and look at more of a real-world example:
function closure(a) { 
  return function trapB (b) {
    return function trapC(c) {
      return c * a + b; 
    }
  }
}

const oneEight = closure(1.8);
const thirtyTwo = oneEight(32);
const degreeToFahrenheit = thirtyTwo(30);
console.log(degreeToFahrenheit); // 86
It can be useful to think of each function declaration as a circle in which each enclosing circle has access to the variables declared in the preceding circle:
In this case, trapC has access to variables a, b and c, while trapB has access to variable and b, and finally the closure has access only to a.

Higher-order functions

Higher-order functions are functions that accept another function as an argument, return another function as a result, or both.
So far, we’ve been using higher-order functions as seen in our closureouterScope,todaysGreeting, and myCounterexamples.
Closures are integral to higher-order functions.
One of the core benefits of higher-order functions is that they allow us to customize the way we call our functions.
Consider the illustration below:
const multiply = (a , b) => {
  return a * b;
}
console.log(multiply(2,3)) // 6
If we’re only interested in getting all the multiples of 2 throughout the entire program, you can repeat 2 as one of the arguments throughout our program:
multiply(2,1) // 2
multiply(2,2) // 4
multiply(2,3) // 6
While this works, it introduces a lot of repetition into our code and it violates the DRY (Don’t repeat yourself) principle.
You could also argue that we can hardcode the value of 2 into our function definition. Well, that’s true, but it’ll make our function less reusable.
Let’s redefine the function to use higher-order functions so we can see the benefits and flexibility it offers when calling the function:
const multiply = (a) => {
    return (b) => {
      return a * b;
    }
  }
Having defined the above function that way, we can create customize function calls as follows:
const multiplyByTwo = multiply(2);
console.log(multiplyByTwo(3)) // 6

const multiplyByThree = multiply(3);
console.log(multiplyByThree(6)); // 18
We can create customize functions that have practical use and also save us the hassle of repeating ourselves.

Currying

Currying is a process that involves the partial application of functions.
A function is said to be curried when all the arguments needed for its invocation have not been supplied. In this case, it will return another function that retains the already-supplied arguments and expect the remaining omitted argument to be supplied before invoking the function.
The function is only invoked when all arguments have been supplied. Otherwise, a new function is returned that retains existing arguments and accepts new arguments as well.
When you curry a function, you call it as f(a)(b)(c)(d) rather than f(a, b, c , d) . By extension, all curried functions are higher-order functions but not all higher-order functions are curried.
The bottom line here is that currying allows us to turn a single function into a series of functions.
Let’s consider the following example:
function sum (a, b) {
  return a + b;
}
console.log(sum(4,5)) // 9
We can go ahead and curry this function so we have the flexibility to call it partially when all arguments aren’t supplied.
function curriedSum (x,y)  {
    if (y === undefined) {
      return function(z) {
        return x + z
      }
    } else {
      return x + y;
    }
  }

 console.log(curriedSum(4, 5)) // 9
console.log(curriedSum(4)(5)) // 9
We don’t have to write another curried implementation of our function every time we need it in order to call it partially. Instead, we can use a general curry function and pass our original function as an argument to it.
Here’s how:
function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
  };
}
Let’s use an example to illustrate how this works.
function mean (a , b, c) {
return (a + b + c) / 3
}
const curriedMean = curry(mean);
console.log(curriedMean(1,2,3))
console.log(curriedMean(1,2)(3))
console.log(curriedMean(1)(2)(3))

Conclusion

As you can see, these concepts build upon one another since closures are used extensively in higher-order functions, and higher-order functions are similar to curried functions.

Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...