Introduction
Functional programming in JavaScript, particularly through the use of thunks and cur- rying, offers developers powerful tools for managing function execution and argument
handling. Thunks are functions designed to encapsulate computations for deferred execution, making them especially useful in scenarios requiring asynchronous oper- ations, such as those commonly encountered in Redux applications. This capability allows developers to streamline complex asynchronous logic and effectively manage side effects, which is crucial in modern web development.[1][2][3] On the other hand, currying is a technique that transforms functions accepting multiple arguments into a series of functions, each taking a single argument. This promotes function reusability, modularity, and improved code readability, aligning with the principles of functional programming.[4][5][6]
Both thunks and currying serve distinct yet complementary purposes. Thunks focus on delaying computation without requiring parameters, facilitating more controlled execution flows, while currying enables partial application of arguments, allowing for specialized functions tailored to specific use cases. The adoption of these techniques can significantly enhance code quality, although they come with considerations regarding complexity and maintainability, especially in larger projects.[1][6][7]
Controversially, while thunks are praised for simplifying asynchronous programming, some critiques highlight the potential for creating unmanageable code structures if overused. Similarly, while currying can lead to cleaner and more maintainable code, it may introduce complexity in function definitions, making it essential for developers to evaluate their practical applications carefully.[8][9][5] Overall, the integration of thunks and currying into JavaScript development exemplifies a broader trend towards functional programming paradigms in the language, offering both advantages and challenges in modern coding practices.
Thunk
A thunk
usually refers to a small piece of code that is called as a function; does some small thing; and then JUMP
s 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.
Thunks
Thunks are a programming construct primarily used to delay computation in function- al programming. They are defined as functions that encapsulate a computation to be performed later, instead of executing it immediately. This allows for greater flexibility in code execution and can help manage side effects, particularly in asynchronous programming contexts[1][10].
Currying
Currying is a fundamental concept in functional programming that involves trans- forming a function with multiple arguments into a series of functions, each taking a single argument. This technique enhances the flexibility, reusability, and modularity of functions in JavaScript, allowing for the partial application of arguments and the creation of specialized versions of functions[4][11].
Comparison of Thunks and Currying
In functional programming, both thunks and currying serve distinct yet complemen- tary purposes in managing function execution and argument handling. Understanding
their differences and use cases can enhance code quality and maintainability in JavaScript.
Thunks
A thunk is a function that encapsulates a computation, allowing it to be executed later. It does not take any parameters and effectively “freezes” the execution context, which can be particularly useful in scenarios where deferred execution is desired.
Thunks can simplify complex operations, particularly in asynchronous programming by providing a way to delay computations until their results are needed, thus improv- ing control over the execution flow[3].
Currying
In contrast, currying is a technique that transforms a function taking multiple argu- ments into a series of functions, each accepting a single argument[6]. This allows for partial application, meaning that a function can be invoked with fewer arguments than it requires, returning another function that awaits the remaining arguments. Currying promotes code reuse and composability, making it easier to create customized functions tailored to specific use cases. It facilitates a more readable code structure, although it can lead to more complex syntax due to the nesting of functions[1][4].
Key Differences
The primary distinction between thunks and currying lies in their approach to function invocation. Thunks encapsulate a computation that can be executed later without re- quiring parameters, whereas currying focuses on transforming how functions accept arguments by allowing them to be applied in a stepwise manner[12]. While thunks are advantageous in handling asynchronous operations and managing side effects, currying enhances function flexibility and reusability.
Use Cases
When deciding between using thunks and currying, developers should consider their specific requirements. Thunks are particularly beneficial in managing asynchronous tasks, such as those found in Redux or other state management libraries, where deferring execution is crucial[3][7]. Conversely, currying shines in scenarios where functions are frequently reused with different argument sets, promoting cleaner and more maintainable code structures[5][6].
Practical Applications
Currying functions serve as a vital tool in functional programming, particularly in JavaScript, where they enhance the modularity and readability of code. By trans- forming functions that accept multiple parameters into a series of functions that each take a single argument, currying facilitates the development of flexible and reusable code structures[13].
Enhancing Function Composition
One of the primary applications of currying is in function composition. This process involves combining multiple functions to create a new function, allowing for cleaner and more manageable code. For example, curried functions enable developers to chain operations seamlessly, where each function can be invoked sequentially with predefined arguments, significantly simplifying the coding process[13][3].
Event Handling in User Interfaces
Currying is particularly beneficial in the context of event handling within user inter- faces. In frameworks like React, developers can leverage curried functions to create specific event handlers by pre-filling certain parameters. This technique reduces code redundancy and enhances maintainability, as the same curried function can be adapted for various events without rewriting logic[13][14].
Simplifying Complex Functions
Another practical application of currying is the simplification of complex functions into a series of unary functions. This transformation allows developers to apply arguments incrementally, making the code more manageable and improving overall clarity. For instance, a curried function can gradually prepare arguments for execution, making it easier for both the original developer and collaborators to understand the function’s purpose and flow[13][15].
Code Reusability
Currying also significantly enhances code reusability by enabling the creation of functions that accept initial parameters while deferring others. This results in versatile code structures that can be employed in various contexts without unnecessary dupli- cation. By allowing functions to be specialized for particular tasks while maintaining a general framework, currying promotes efficient coding practices[13].
Read More On: What the heck is a ‘thunk’ and what’s ‘Currying’?