When discussing functional programming we often talk about the machinery, and not the core principles. Functional programming is not about monads, monoids, or zippers, even though those are useful to know. It is primarily about writing programs by composing generic reusable functions. This article is about applying functional thinking when refactoring TypeScript code.

And to do that we will use the following three techniques:

Use Functions Instead of Simple Values

Model Data Transformations as a Pipeline

Extract Generic Functions

Let’s get started!

Suppose we have two classes: Employee and Department. Employees have names and salaries, and departments are just simple collections of employees.

The averageSalary function is what we are going to refactor.

The function takes a list of employees, a minimum salary, and optionally a department. Given a department, it calculates the average salary of the employees in that department. When not given a department, it does the same calculation for all the employees.

This function can be used as follows.

Despite the straightforward requirements, the code we got is convoluted, not to mention hard to extend. If I just added another condition, the signature of the function (thus, the public interface) would have to change, and the if statement would grow into a real monster.

Let’s apply some functional programming techniques to refactor this function.

Use Functions Instead of Simple Value

Using functions instead of simple values may seem counterintuitive at first, but it is actually a very powerful technique for generalizing code. In our case it means replacing the minSalary and department parameters with two functions checking the conditions.

What we have done is that we have unified the interfaces of the salary and department conditions. Whereas before both the conditions were implemented ad-hoc, now they are explicitly defined and conform to the same interface. This unification allows us to pass all the conditions as an array.

Since an array of conditions is nothing but a composite condition, we can pull out a simple combinator making it explicit.

It is worth noting that the ‘and’ combinator is generic, and, therefore, can be reused and potentially extracted into a library.

Intermediate Results

The ‘averageSalary’ function has already got more robust. A new condition can be added without breaking the interface of the function or changing its implementation.

Model Data Transformations as a Pipeline

Another useful practice of functional programming is modeling all data transformations as a pipeline. Which in our case means extracting the filtering out of the loop.

This change made the counting unnecessary.

Next, if we pluck the salaries before adding them up, the summation will become a simple reduce.

Extract Generic Functions

The next observation is that the last two lines have nothing to do with our domain. There is nothing there about employees or departments. It is essentially an implementation of the average function. So let’s make it explicit.

Once again, the extracted function is absolutely generic.

Finally, after pulling out the plucking of salaries, we get our final solution.

Comparing the original and final solutions I can say without a doubt that the latter is superior. First, it is more generic (we can add new types of conditions without breaking the interface of the function). Second, we got rid of the mutable state and if statements, which made the code easier to read and understand.

Know When to Stop

The functional programming style is about writing a lot of small functions that take collections of values and return new collections of values. These functions can be reused and composed in different ways — that’s good. One downside of this style, though, is that the code may get too abstract, so it is hard to understand what all these functions are doing together.

I like to use the Lego analogy: Lego pieces can be combined in a lot of different ways — they are composable. But note that not all the pieces are of size one. So when refactoring using the techniques described in this post, don’t try to make everything a function that takes an Array<T> and returns an Array<U>. True, such functions will be extremely composable, but they would significantly hinder the ability to understand what’s going on.

Summing Up

In this article I showed how to apply functional thinking when refactoring TypeScript code. I did that by taking a simple function and transforming it using the following rules:

Use Functions Instead of Simple Values

Model Data Transformations as a Pipeline

Extract Generic Functions

Read More

Highly recommend checking out the following two books: