Skip to content

Ruairidh Codes

What is a functor in JavaScript?

functional, functional programming, patterns, javascript2 min read

Pretty much every day, it's likely that you use helpful functions like map, reduce, filter, etc in your work. They're great! They save time, look neat, and you get to tell your peers that you're really into functional programming.

It sounds impressive, but do you ever feel like you're not entirely sure of yourself when you hear senior engineers talk about things like partial application, or functors?

Today, we're going to learn what is a functor in JavaScript and this will be one of what will be several articles on more advanced functional concepts like functors, monoids, semigroups, and more.

What is a functor in JavaScript?

TL;DR, it's an object with a map method on it which generates another object of the same type. NB: map here is just a standard function name for a morphism (a map between objects). While there are strong similarities with Array.map() which you'll see below, don't get too hung up on the naming.

Yeah, not super helpful, so let's look at some practical examples which will help you write some really clean code at work!

Imagine you have some code at work which needs to transform a title for SEO purposes. Your code looks like this:

1const transformTitle = (string) => {
2 const uppercased = string.toUpperCase();
3 const withoutWhiteSpace = uppercased.trim();
4 const addBrand = withoutWhiteSpace.concat("- my cool brand");
5 return addBrand;
6};
7
8// Input
9a string
10
11// Output
12A STRING - my cool brand

This works fine, but it's a bit messy. It can't easily be chained, and the transformations are heavily dependant on one another. There must be a better way, right?

Enter, the Identity Functor

Functors sounds scary, but they're actually pretty simple for our purposes. We want to create a placeholder for a value. We then pass the function to the value, rather than passing the value to the function.

That's maybe a bit strange to read and understand, so let's look at an example:

1const Container = (x) => ({
2 map: (f) => Container(f(x)),
3 toString: () => `${x}`,
4});

This looks recursive, but it actually isn't. We're just placing our function within the Container. It runs, but within the boundaries of Container. This will allow us to continue to chain our functions.

So if we ran:

const result = Container(["my text"]).map((x) => x.toUpperCase())

We'd get:

Output: ["MY TEXT"]

See how it's stored in the array still? We've retained our original structure. So now we can do something like:

1const result = (text) => {
2 return Container(text)
3 .map((x) => x.toUpperCase())
4 .map((x) => x.trim())
5 .map((x) => x.concat("- my cool brand"))
6 .toString();
7};
8
9result("my text");
10
11// Output: ["MY TEXT - my cool brand"];

So we've created a clean, composable linear control flow. But we may also want a nice way to return the contents of our container. Let's add a new method for that:

1const Container = (x) => ({
2 map: (f) => Container(f(x)),
3 fold: (f = (x) => x) => f(x),
4 toString: () => `${x}`,
5});

So here, we introduce fold. This is pretty simple, we're just passing the function and its value, and not wrapping it in a new Container. So if we revisit our new way of generating a title, we'd get:

1const result = (text) => {
2 return Container(text)
3 .map((x) => x.toUpperCase())
4 .map((x) => x.trim())
5 .map((x) => x.concat("- my cool brand"))
6 .fold()
7 .toString();
8};
9
10result("my text");
11
12// Output: MY TEXT - my cool brand

Isn't this great? We've gone from a stateful, messy, and fragile procedural function, to a clean and composable functor.

Each time we run our map method, we can rely on its output. You don't need to worry about running things in the wrong order, or managing the state of the function. There are no side effects. You also have a cleaner syntax which is a much nicer developer experience.

So that's a functor in JavaScript?

Yes! Really, it's just a placeholder for a value, so you can pass a function to a value and manipulate the function.

And the best bit is that you've already been using functors without realising. Remember at the start when I said you had used Array.map(). Well, that's a functor. More specifically, Array is a functor as it implements a map method. Take our example above and swap Container to Array and the result is largely the same.

So now you know how to use a functor in JavaScript, what a functor is, and how to apply the concept to refactor fragile code to become clean, composable, and functional!

You can play around with a working version of this here

Found this helpful? Let me know on Twitter: @ruairidhwm or sign up to my newsletter below for more content like this.

Subscribe to the Newsletter

Subscribe to get my latest content by email.

    I won’t send you spam.

    Unsubscribe at any time.



    © 2021 by Ruairidh Wynne-McHardy. All rights reserved.