Thunk and Currying – Know Your JavaScript!

Thunk

thunk usually refers to a small piece of code that is called as a function; does some small thing; and then JUMPs to another location; which is usually a function; instead of returning to its caller. Assuming the JUMP target is a normal function, when it returns, it will return to the thunk’s caller.

The word thunk has at least three related meanings in computer science. A “thunk” may be:

  • a piece of code to perform a delayed computation (similar to a closure)
  • a feature of some virtual function table implementations (similar to a wrapper function)
  • a mapping of machine data from one system-specific form to another, usually for compatibility reasons

In general it refers to any way to induce dynamic behaviour when referencing an apparently static object. Brian Wichmann is the one who invented the term. When he was asked to explain pass-by-name said “Well you go out to load the value from memory and then suddenly – thunk – there you are evaluating an expression.”

The term has come to be generalised beyond pass-by-name. It now includes any situation in which an apparently or nominally static data reference induces dynamic behaviour. When we have some computation like adding 3 to 5 in our program, then creating a thunk of it means not to calculate it directly, but instead create a function with zero arguments that will calculate it when the actual value is needed.

In JavaScript, a thunk is a function that is used to delay the evaluation of an expression. It’s a way to create a function that can be called later, with a specific set of arguments, rather than being evaluated immediately.

Thunks are often used as a way to perform asynchronous tasks, such as making network requests or reading from a database. They can also be useful for optimizing code by allowing you to avoid unnecessary calculations or function calls until they are actually needed.

Let’s see some examples!

(let ((foo (+ 3 5))) ; the calculation is performed directly, foo is 8
  ;; some other things
  (display foo)) ; foo is evaluated to 8 and printed

(let ((foo (lambda () (+ 3 5)))) ; the calculation is delayed, foo is a
                                 ; function that will perform it when needed
  ;; some other things
  (display (foo))) ; foo is evaluated as a function, returns 8 which is printed

In the second case, foo would be called a thunk.

A zero-argument function has no way to change its behaviour based on parameters it is called with, since it has no parameters, therefore the entire operation of the function is set – it is just waiting to be executed. No more “thought” is required on the part of the computer, all of the “thinking” has been done – the action is completely “thunk” through. In computer programming, a thunk is a subroutine that we use to inject an additional calculation into another subroutine. Thunks are primarily used to delay a calculation until its result is needed, or to insert operations at the beginning or end of the other subroutine.

So, what is a thunk really? It’s simply a function returned from another function. Let’s look at a quick example in JavaScript:

function delayedLogger(message, delay) {
  return function(logger) {
    setTimeout(function() {
      logger(message);
    }, delay);
  };
}

In this example, when we call the delayedLogger function, it returns a thunk. We can then pass that thunk a logger parameter, which will be executed after the specified delay.

const thunk = delayedLogger('See you in a bit', 2000);
thunk(console.log);

In this example, we’ll see "See you in a bit" logged to the console after two seconds.

A thunk is another word for a function. But it’s not just any old function. It’s a special (and uncommon) name for a function that’s returned by another. Like this:

function wrapper_function() {
  // this one is a "thunk" because it defers work for later:
  return function thunk() {   // it can be named, or anonymous
    console.log('do stuff now');
  };
}

Currying

Currying is a technique in functional programming that involves breaking down a function that takes multiple arguments into a series of functions that each take a single argument. It’s a way to create a new function from an existing function by partially applying some of its arguments. Currying is converting a single function of n arguments into n functions with a single argument each. So for example if we have a function that takes three parameters a,b & c

let result = fn(a,b,c)

When we apply currying to it, then it becomes

let applyCurrying = curriedFn(fn);
let result = applyCurrying(a)(b)(c);

A trivial implementation of curriedFn:

const curriedFn = a => b => c => ( do compuation with a,b,c );

In currying, each function takes exactly one parameter. So, It’s all about composition. Currying allows us to create new functions with some predefined data in their closure scope.
Let’s look at some examples of how we can use this to write some more concise and performant code.

Pros of using currying in JavaScript:

  • Allows you to create more flexible and reusable functions
  • Makes it easier to write code that is easy to read and understand
  • Can help you avoid unnecessary calculations or function calls

Cons of using currying in JavaScript:

  • Can be confusing for beginners or those unfamiliar with functional programming concepts
  • May result in longer and more complex code, depending on the use case
  • May not be necessary or beneficial in all situations

Remove repetition

In this example, we need to write some code that generates a list of URLs.

const createURL = (baseURL, path) => {
  const protocol = "https";
  return `${protocol}://${baseURL}/${path}`;
};

// create URLs for our main site
const homeURL = createURL("mysite.com", "");
const loginURL = createURL("mysite.com", "login");
const productsURL = createURL("mysite.com", "products");
const contactURL = createURL("mysite.com", "contact-us");

// create URLs for our careers site
const careersHomeURL = createURL("mysite-careers.com", "");
const careersLoginURL = createURL("mysite-careers.com", "login");

There are some opportunities to improve the code above by using currying! Notice how we are constantly repeating the baseURL argument. Let’s fix that.

const createURL = baseURL => {
  const protocol = "https";

  // we now return a function, that accepts a 'path' as an argument
  return path => {
    return `${protocol}://${baseURL}/${path}`;
  };
};

// we create a new functions with the baseURL value in it's closure scope
const createSiteURL = createURL("mysite.com");
const createCareersURL = createURL("mysite-careers.com");

// create URLs for our main site
const homeURL = createSiteURL("");
const loginURL = createSiteURL("login");
const productsURL = createSiteURL("products");
const contactURL = createSiteURL("contact-us");

// create URLs for our career site
const careersHomeURL = createCareersURL("");
const careersLoginURL = createCareersURL("login");

Currying allows us to compose new functions, createSiteURL and createCareersURL. These new functions are pre-populated with the ‘mysite.com’ and ‘mysite-careers.com’ baseURLs in their closure scopes.
This means we remove the need to repeatedly declare the same baseURL argument on multiple functions.

Thunk is a term used in computer science to refer to a small piece of code that is called as a function, does a small task and then jumps to another location, which is usually a function, instead of returning to its caller. Thunks are often used as a way to perform asynchronous tasks, such as making network requests or reading from a database. They can also be used to optimize code by avoiding unnecessary calculations or function calls until they are needed. In JavaScript, a thunk is a function that is used to delay the evaluation of an expression. Currying is a technique in functional programming that involves breaking down a function that takes multiple arguments into a series of functions that each take a single argument. It allows for the creation of more flexible and reusable functions and makes it easier to write code that is easy to read and understand. However, it can result in longer and more complex code and may not be necessary or beneficial in all situations.

Leave a Comment