Over-Thunking It

With the increasing adoption of functional programming there are strange terms slowly moving their way from academic obscurity to mainstream software engineering. Thanks mainly to the React and Redux frameworks the term Thunk is increasingly en vogue.

The Wikipedia definition is as accurate as it is unhelpful, so let's explain what a Thunk is using contemporary examples.

Using Thunks in Unit Tests

Let's say we are testing a MyWidget component written in JavaScript. Using Jasmine we want to ensure that if we try to create a MyWidget instance with undefined then an exception is thrown. The test might be written as follows:

it("Throws when created with undefined", function() {
    var tst = function() { new MyWidget(undefined); };
    expect(tst).toThrow();
});

Note that we need to pass a reference to a function into expect so that it can:

  1. Execute the function within Jasmine
  2. Catch and swallow any exception thrown
  3. Fail the test if an exception is not thrown

As of ES6 we can take advantage of arrow functions to write the code like this:

it("Throws when created with undefined", function() {
    const tst = () => new MyWidget(undefined);
    expect(tst).toThrow();
});

A common error is to write

expect(new MyWidget(undefined)).toThrow();

...which gives an error when either the exception is thrown, or the framework tries to invoke the MyWidget object as a function. The crucial point is that we need to introduce an anonymous function for deferred execution i.e. to allow Jasmine to pick the right time to execute the code that may or may not throw.

That in essence is the purpose of a thunk - it's a function (or arrow, or lambda, or code block etc...) that takes no input and allows the evaluation of an expression to be postponed until needed. You use a thunk when there is a delay between the time you want to specify a value and the time you want to produce the value.

Using Thunks in React

To take a slightly more complex example consider a component in React. Every React component can have three features:

  1. A set of read-only properties passed by the parent
  2. A render method that produces nodes to be added to the (Virtual) DOM
  3. A state object that contains all the state of the component relating to its visual appearance

When you want to change the state of the component you do it as follows:

this.setState({foo: bar})

As opposed to:

this.state.foo = bar

The reason for the indirection is that in addition to updating the state we want to mark the component as being dirty, so that React knows to re-render it and all its children.

So far so good, but let's say we want to update a numeric property to itself plus one. This seems reasonable:

this.setState({foo: this.state.foo + 1})

But code like this may fail since React may batch multiple calls to setState together and update the state asynchronously. So the code may work most of the time but fail if any concurrency is introduced (it's a heisenbug). The solution is what is referred to as Functional SetState:

this.setState((oldState, props) => {
    return {foo: oldState.foo + 1};
})

React will queue up the functions you have provided and execute them in order, ensuring that the eventual value of foo is always correct.

Using Thunks in Redux

Redux provides an additional data store which can be used from both React and Angular UI's. When combined with React it avoids the need to pass data down though the properties of a tree of components and opens up intriguing possibilities such as a 'time travelling' debugger.

For the current discussion all you need to know about Redux is that you change the state of the Redux container by dispatching an Action. An action is just a JavaScript object with a type property (plus whatever else you like). So for example:

this.store.dispatch({type: "newOrder", value: theOrder})

The code to build an action is usually refactored into separate methods, such as:

const orderActionCreator = (theOrder) => {
    return {type: "newOrder", value: theOrder}
}

These action creator methods are typically where you would make calls to RESTful services, but given that these calls may take time and fail to complete we need a way to notify the UI during the different stages of processing. This functionality is provided by the Redux-Thunk library, which allows you to return thunks from your action creator methods. These in turn can invoke dispatch multiple times.

Consider the code below:

const orderActionCreator = (theOrder) => {
    return (dispatch) => {
        dispatch({type: "order_in_progress"});
        myAjax.put(theOrder)
              .then(resp => dispatch({type: "order_sent"}))
              .error(ex => dispatch({type: "order_failed"}))
    }
}

Our action creator method now returns a thunk. The thunk initially dispatches an 'in progress' action, which will change the state in Redux and indirectly cause a "please wait" message to be displayed in the UI. Then we use our AJAX library of choice to PUT the new order to the server. The AJAX call returns a Promise, to which we can attach event handlers for success and failure. Each of these in turn dispatches the appropriate acton.

Built in Support for Thunks in Scala

The Scala language has built-in support for thunks, under the name of nullary functions (full disclosure - I'm an unrepentant Scala fanboy). Instead of passing a lambda into a function like this:

func(() => 123)

You can pass a thunk using the code block syntax familiar to Ruby or Groovy developers:

func { 123 }

Thunks have two key differences from lambdas:

  1. A thunk is invoked when the name is used. For example if foo referenced a lambda you would need foo() to invoke it, whereas with thunks foo by itself is enough. This fits in with the idea that a thunk is just a value, albeit one that is calculated on demand.
  2. A thunk is invoked every time is name is used. So in the example above if you used foo ten times in a method then every use would be an invocation.

To see what I mean examine the program below and its output:

object Program {
    def func(thunk: => Int): Unit = {
        println(thunk)
        println(thunk * 2)
        println(thunk * 3)
    }
    def main(args: Array[String]): Unit = {
        func {
            println("thunk invoked")
            12
        }
    }
}
thunk invoked
12
thunk invoked
24
thunk invoked
36

Hopefully this article has cleared up an 'obscure but becoming less so' part of the FP landscape. Future posts in this series will cover Monads, Monoids, Lenses and various other creatures from the bestiary of FP.

Article By
blog author

Garth Gilmour

Head of Learning