DEV Community

Ayron Wohletz
Ayron Wohletz

Posted on • Originally published at funtoimagine.com

Getting started with functional programming in JavaScript and TypeScript

Mastering these four rules of thumb can decrease your JS/TS code volume by 5x (as argued by before/after comparison.) And your code will become easier to read, write, and maintain (as argued in Favor values over variables.) You'll be well on your way to reaping the benefits of functional programming in JS/TS.

Favor const over let

Using const instead of let forces you to stop relying on variables to get things done.

In TypeScript, you can use Readonly types to further enforce immutability. For example, the following is valid code in JavaScript. Despite the const, we can still change result:

const result = [];

for (let todo of allTodos) {
    if (!todo.completed) {
        result.push(todo);
    }
}
Enter fullscreen mode Exit fullscreen mode

But with TypeScript we can make it a compile-time error:

const result: ReadonlyArray<Readonly<Todo>> = [];

for (let todo of allTodos) {
    if (!todo.completed) {
        result.push(todo); // ERROR! Property 'push' does not exist on type 'readonly Readonly []'.
    }
}
Enter fullscreen mode Exit fullscreen mode

So I encourage using const paired with Readonly, ReadonlyArray, and the readonly keyword in TypeScript. They're like training wheels for learning to program in a functional style.

Replace loops with transformations

When you first adopt const and Readonly, you might wonder how to actually get things done. It's awkward at first, but becomes fast and fluid with practice.

The low-hanging fruit is knowing how to use map, filter, and reduce. In the above case, we can use the Array.filter method:

const incompleteTodos: ReadonlyArray<Readonly<Todo>> = allTodos.filter(todo => !todo.completed);
Enter fullscreen mode Exit fullscreen mode

If I gave a rough estimate, I would say that just this one practice of replacing common imperative loops with transformations (i.e. Array methods or Lodash functions) can reduce code volume by 5x. Here, instead of 5 lines for the for-loop, we have one line. And this is just a simple filter. More complex transformations require ever more intricate loops. I won't clutter this post with more examples to prove the point, but I encourage you to try it for yourself.

Go further along these lines, and a need will arise for a higher-level library than mere Array methods.

For one, some methods in JS mutate their objects. For example, if we wanted to sort the result array, we could use Array.sort, but that changes result. That breaks our rule of keeping values immutable. And indeed TypeScript does not allow calling sort on ReadonlyArray.

For two, some patterns and algorithms repeat often enough that we should make them their own functions. E.g. intersection, uniq, or flatMap.

This is why I recommend Lodash.

Use Lodash (or a similar library)

Lodash makes it straightforward to keep your values immutable in most cases. As you program with const, you will find many cases where Lodash has a convenient function available. E.g. for the sorting mentioned above, we could use sortBy. Or what if we wanted to display only uniquely-titled TODOs?

const incompleteTodos: ReadonlyArray<Readonly<Todo>> = allTodos.filter(todo => !todo.completed);
const uniqTodos = uniqBy(incompleteTodos, todo => todo.title);
Enter fullscreen mode Exit fullscreen mode

Many times I've seen JavaScript programmers re-inventing what Lodash already offers. This leads to code bloat and error-prone implementation.

Lodash does have some impure functions, e.g. remove. You could try a library that enforces functional style more strictly, such as Ramda. I stick to Lodash though, because it has better TypeScript support, and it's more familiar for devs I may be working with who don't come from a functional programming background.

Even if you do use Lodash though, JavaScript still has some rough edges around its syntax in terms of functional programming. One of these is conditionals – if-statements and switches.

Replace conditionals with functions

Sadly, JS does not have conditional expressions, besides the ternary operator. Only conditional statements. So we can't do something like this:

const message = if (userLoggedIn) {
    `Hello ${user}`
} else if (userNew) {
    `Set up your account`
} else {
    `Unrecognized user`
}
Enter fullscreen mode Exit fullscreen mode

Nor can we do something like this:

const label = switch (type) {
    case "todo":
    case "task":
        "Task"
    case "activity":
        "Activity"
}
Enter fullscreen mode Exit fullscreen mode

Instead we end up with this:

let message = `Unrecognized user`

if (userLoggedIn) {
    message = `Hello ${user}`
} else if (userNew) {
    message = `Set up your account`
}
Enter fullscreen mode Exit fullscreen mode

Which of course breaks the rule of using const instead of let.

Or end up with nested ternary operators, which quickly gets unreadable:

const message = userLoggedIn ? `Hello ${user}` : (userNew ? `Set up your account` : `Unrecognized user`)
Enter fullscreen mode Exit fullscreen mode

The simple solution is to extract the conditional out into its own function:

function getUserMessage(userLoggedIn: boolean, userNew: boolean): string {
    if (userLoggedIn) {
        return `Hello ${user}`
    } else if (userNew) {
        return `Set up your account`
    } else {
        return `Unrecognized user`
    }
}

const message = getUserMessage(userLoggedIn, userNew)
Enter fullscreen mode Exit fullscreen mode

Or

function getActivityLabel(type: string): string {
    switch (type) {
        case "todo":
        case "task":
            return "Task"
        case "activity":
            return "Activity"
    }
}

const label = getActivityLabel(type)
Enter fullscreen mode Exit fullscreen mode

This preserves immutability. And has the added benefit of giving the conditional a descriptive name and making it easily unit-testable.

Summary

These rules of thumb lay a foundation for functional thinking in JavaScript/TypeScript. They lead to considerable benefits in everyday code. And mastering them clears the way to higher-level functional patterns. I'll write about those patterns in future posts.

If you have questions about converting some imperative code to this style, let me know.

Discussion (0)