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.

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 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.

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.