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:
const transformTitle = (string) => {
const uppercased = string.toUpperCase();
const withoutWhiteSpace = uppercased.trim();
const addBrand = withoutWhiteSpace.concat("- my cool brand");
return addBrand;
};
// Input
a string
// Output
A 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:
const Container = x => ({
map: f => Container(f(x)),
toString: () => `${x}`,
})
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:
const result = text => {
return Container(text)
.map(x => x.toUpperCase())
.map(x => x.trim())
.map(x => x.concat("- my cool brand"))
.toString()
}
result("my text")
// 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:
const Container = x => ({
map: f => Container(f(x)),
fold: (f = x => x) => f(x),
toString: () => `${x}`,
})
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:
const result = text => {
return Container(text)
.map(x => x.toUpperCase())
.map(x => x.trim())
.map(x => x.concat("- my cool brand"))
.fold()
.toString()
}
result("my text")
// 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.