Currying and Composition
Introduction: Building Blocks For Flexible Code
In our previous lessons, we covered how functions can capture state through closures and how pure functions allow us to handle data without causing side effects. You have already seen how to process lists of data using tools like map and reduce. Now, we are going to learn how to snap these concepts together like building blocks.
The goal of this lesson is to make your functions even more flexible and reusable. By the end of this session, you will understand how to break complex functions into smaller steps and how to link those steps together to create a smooth data pipeline. This approach makes your code look less like a series of instructions and more like a clear story of how your data is being transformed.
Understanding Currying
Normally, when you write a function in JavaScript, you give it all its arguments at once. For example, a function to calculate tax might look like calculateTax(rate, amount). Currying is a technique where we change a function so that instead of taking all arguments at one time, it takes them one by one.
When you curry a function, calling it with the first argument returns a new function that waits for the next argument. This is possible because of closures, which we studied in the first lesson. The inner function "remembers" the first value you gave it. This allows you to create specialized versions of a function that are pre-filled with some of the data they need.
A Simple Curry Helper
While we can write curried functions manually, it is helpful to use a utility that can turn a standard function into a curried one. This helper checks how many arguments the original function expects and decides whether to run the function or return another function to collect more data.
In this code, the helper uses the length property of a function to see how many arguments it needs. If the number of arguments provided is enough, it executes the function. If not, it returns a new function that collects the remaining arguments and merges them with the ones already provided.
Note: This
simpleCurryimplementation is designed for fixed-arity pure functions (functions with a specific, set number of arguments). In real-world production code, you should be aware thatfn.lengthdoes not count default parameters or rest parameters (...args). It also doesn't handle thethiscontext, though in functional programming, we typically avoidthisin favor of pure data transformation.
Partial Application In Practice
Once we have our simpleCurry helper, we can use it to create specialized functions. This process is called partial application. It means we are applying some of the arguments now and leaving the rest for later. This is very useful when you have a general calculation but want to reuse it with a specific setting.
In this example, we created a general tax function that takes a rate and an amount. By calling tax(0.2), we created a new, specialized function called withVAT. This new function only needs the amount to finish its job because the 0.2 rate is already "baked in" inside the closure. This makes the code much cleaner when you need to apply the same tax rate to many different prices.
Function Composition: Pipe And Compose
Function composition is the process of combining two or more functions to produce a new function. Instead of nesting functions inside each other like f(g(h(x))), we use helpers called pipe and compose to chain them. This keeps our code readable and prevents the "pyramid" effect of deeply nested parentheses.
The pipe function processes data from left to right. It is very natural to read because it follows the order of how we think: first do this, then do that. It uses reduce to pass the result of one function as the input to the next. The compose function does exactly the same thing but in the opposite direction, from right to left, using reduceRight. This matches the way mathematical functions are often written. Both tools allow you to build complex logic by simply listing the names of the small functions you want to use.
Building A Complete Pipeline
Now we can combine everything we have learned to build a full data processing pipeline. We will take a raw number and transform it through several steps: adding tax, rounding the result, and finally formatting it as a currency string. This shows the true power of functional programming because each step is a small, pure function that does one thing well.
The finalPrice function created with pipe takes the input 49.99 and sends it to withVAT. The result of that calculation is then automatically passed to round2, and the rounded number is finally passed to formatUSD. The finalCompose version does the exact same work but requires the functions to be listed in reverse order. Both versions produce a clean, formatted string without requiring us to manage intermediate variables or complex loops.
Summary And Practice Preparation
In this lesson, we expanded our functional programming toolkit with currying and composition. We learned how to use currying to create specialized functions like withVAT by pre-filling arguments. We also explored how pipe and compose allow us to link simple, pure functions together into powerful pipelines that are easy to read and maintain.
These patterns are the hallmark of professional JavaScript development. They help you write code that is modular, where every piece can be tested on its own and then combined with others to solve bigger problems. As you move into the practice exercises, focus on how the data flows from one function to the next. In the upcoming practice exercises, you will implement and use these helpers yourself. Good luck with the practice!
