## DEV Community

Matthew Staniscia

Posted on

# Functors and Monads in Javascript

The purpose of this talk is to shed some light on some of the Functional Programming terms we see thrown about here and there, primarily `Functor` and `Monad`.

What the heck are these? Let's start with 2 phrases that I saw while scouring the internet.

"A `functor` is something that you can `map`."

"A `monad` is a `functor` that you can `flatMap`."

Let's dive into it.

## Functors

In mathematics, specifically category theory, a functor is a map between categories.

In mathematics, a category (sometimes called an abstract category to distinguish it from a concrete category) is a collection of "objects" that are linked by "arrows".

Confused? Good.

Simply put a category is a `collection of objects` let's call that something, and a functor is a map between `collections of objects`.

So that brings us to our first statement:

"A `functor` is something that you can `map`."

Let's look at some code:

``````const collection1 = [1, 2, 3] // [1,2,3]
const collection2 = collection.map(x => x + 1) // [2,3,4]
``````

Here we have an array (a collection of Ints). Since we can map collection1 to collection2 by doing `x => x + 1` we can say that Arrays in JS are `Functors`.

Lets say we wanted to create our own functor. This functor will represent a Person Object.

``````const p1 = {
firstName: 'matthew',
lastName: 'staniscia',
hairColor: 'brown',
age: 37,
}

const Person = value => ({
value,
})

Person(p1)

/*
Result
{
value:{
firstName: 'matthew',
lastName: 'staniscia',
hairColor: 'brown',
age: 37
}
}
*/
``````

This is not a functor yet because we can't yet map over it. So lets add a mapping function to it.

``````const p1 = {
firstName: 'matthew',
lastName: 'staniscia',
hairColor: 'brown',
age: 37,
}

const Person = value => ({
map: fn => Person(fn(value)),
value,
})

Person(p1)

/*
Result
{
map: [Function: map],
value:{
firstName: 'matthew',
lastName: 'staniscia',
hairColor: 'brown',
age: 37
}
}
*/
``````

We can now map some functions to it.

``````const objectMapper = fn => value =>
Object.keys(value).reduce((acc, cur, idx, arr) => ({ ...acc, [cur]: fn(value[cur]) }), value)

const makeUpper = s => (typeof s === 'string' ? s.toUpperCase() : s)

Person(p1).map(x => objectMapper(y => makeUpper(y))(x))
Person(p1).map(x => objectMapper(makeUpper)(x))
Person(p1).map(objectMapper(makeUpper))

/*
Result for all 3 calls
{
map: [Function: map],
value:{
firstName: 'MATTHEW',
lastName: 'STANISCIA',
hairColor: 'BROWN',
age: 37
}
}
*/
``````

Let's try mapping a couple functions together.

``````const objectMapper = fn => value =>
Object.keys(value).reduce((acc, cur, idx, arr) => ({ ...acc, [cur]: fn(value[cur]) }), value)

const makeUpper = s => (typeof s === 'string' ? s.toUpperCase() : s)

const checkAge = n => (typeof n === 'number' ? (n <= 35 ? [n, 'You is good.'] : [n, 'You is old.']) : n)

Person(p1)
.map(objectMapper(makeUpper))
.map(objectMapper(checkAge))

/*
Result
{
map: [Function: map],
value:{
firstName: 'MATTHEW',
lastName: 'STANISCIA',
hairColor: 'BROWN',
age: [ 37, 'You is old.' ]
}
}
*/
``````

This object is now a functor because it is something that we can map. Now it's time to turn it into a Monad.

Let's go back to the definition of a Monad from earlier.

"A `monad` is a `functor` that you can `flatMap`."

### What is flatMap?

In short when you flatMap something, you will run a map function and then flatten it.

In the case of our Person object our output will not look like `Person({...stuff...})` but rather `{...stuff...}`.

We use flatMap to pull out the result of the map from its context. Other names for flatMap are `chain` and `bind`.

Back to code.

``````const Person = value => ({
map: fn => Person(fn(value)),
chain: fn => fn(value),
value,
})
``````

Well that looks simple enough. Since we're mapping and taking the value out of context we only need to return the unwrapped value. Let's see it in action.

``````Person(p1).chain(objectMapper(makeUpper))

/*
Result
{
firstName: 'MATTHEW',
lastName: 'STANISCIA',
hairColor: 'BROWN',
age: 37
}
*/

Person(p1)
.chain(objectMapper(makeUpper))
.chain(objectMapper(checkAge))

/*
Result

TypeError: Person(...).chain(...).chain is not a function
*/
``````

Huston, we have a problem. What happening here? Why is it wrong?
It's simple. The return of the first chain is no longer a Person Monad, it's just a JSON string, so trying to chain it again won't work, if we wanted to chain on a chain we need to maintain context.

``````Person(p1)
.chain(x => Person(objectMapper(makeUpper)(x)))
.chain(objectMapper(checkAge))

/*
Result
{
firstName: 'MATTHEW',
lastName: 'STANISCIA',
hairColor: 'BROWN',
age: [ 37, 'You is old.' ]
}
*/
``````

But isn't that the same as this?

``````Person(p1)
.map(objectMapper(makeUpper))
.chain(objectMapper(checkAge))
``````

Yes. Since map keeps context we can map or chain on that context.

I think we've seen something like this before...

For an object to be a monad is must satisfy 3 monadic laws.

• Left identity
• Right identity
• Associativity
``````// testing monad rules
const x = 'Matt'
const f = x => Person(x)
const g = x => Person(x + ' is kool')

const LI1 = Person(x).chain(f)
const LI2 = f(x)

const RI1 = Person(x).chain(Person)
const RI2 = Person(x)

const AC1 = Person(x)
.chain(f)
.chain(g)
const AC2 = Person(x).chain(x => f(x).chain(g))

// Left Identity
// f being a function returning a monad
Object.entries(LI1).toString() === Object.entries(LI2).toString()

// Right Identity
Object.entries(RI1).toString() === Object.entries(RI2).toString()

// Associativity
// f and g being functions returning a monad
Object.entries(AC1).toString() === Object.entries(AC2).toString()

/*
Result
true
true
true
*/
``````

In the case of our Person monad it satisfies these rules.

You don't need to use Monads. If you do use Monads and write all of your Monads in the same manner, then you'll have a structure that you can chain together and intermix as you want. Monads are pretty much a design structure that can be used to help you track context so that your code is clear and consistent.

Let's take a look at a basic example of different monads being used together. These are very rudimentary monads but they'll get the point across.

We'll create 3 more monads `Child`, `Teen`, and `Adult`. These monads will have some properties that you can access if you want to be able to know if it's a `Child`, `Teen`, or `Adult`.

``````const Person = value => ({
map: fn => Person(fn(value)),
chain: fn => fn(value),
value,
})

const Adult = value => ({
chain: fn => fn(value),
isChild: false,
isTeen: false,
value,
})

const Teen = value => ({
map: fn => Teen(fn(value)),
chain: fn => fn(value),
isChild: false,
isTeen: true,
value,
})

const Child = value => ({
map: fn => Child(fn(value)),
chain: fn => fn(value),
isChild: true,
isTeen: false,
value,
})
``````

We'll also add the functions that we will use to map and / or chain.

``````const objectMapper = fn => value =>
Object.keys(value).reduce((acc, cur, idx, arr) => ({ ...acc, [cur]: fn(value[cur]) }), value)

const makeUpper = s => (typeof s === 'string' ? s.toUpperCase() : s)

const makeLower = s => (typeof s === 'string' ? s.toLowerCase() : s)

const makeCapitalize = s => (typeof s === 'string' ? s.replace(/(?:^|\s)\S/g, a => a.toUpperCase()) : s)

const setContext = obj => (obj.age < 13 ? Child(obj) : obj.age < 18 ? Teen(obj) : Adult(obj))

const agePerson = age => obj => setContext({ ...obj, age: addAge(obj.age)(age) })
``````

Let's start playing with our monads.

``````const p1 = {
firstName: 'matthew',
lastName: 'staniscia',
hairColor: 'brown',
age: 10,
}

Person(p1).map(objectMapper(makeUpper))

/*
Result: This is a Person Monad
{
map: [Function: map],
chain: [Function: chain],
value:
{
firstName: 'MATTHEW',
lastName: 'STANISCIA',
hairColor: 'BROWN',
age: 10
}
}
*/

Person(p1)
.map(objectMapper(makeUpper))
.chain(setContext)

/*
Result: This is a Child Monad
{
map: [Function: map],
chain: [Function: chain],
isChild: true,
isTeen: false,
value:
{
firstName: 'MATTHEW',
lastName: 'STANISCIA',
hairColor: 'BROWN',
age: 10
}
}
*/

Person(p1)
.map(objectMapper(makeUpper))
.chain(setContext)
.chain(agePerson(4))
.map(objectMapper(makeLower))

/*
Result: This is a Teen Monad
{
map: [Function: map],
chain: [Function: chain],
isChild: false,
isTeen: true,
value:
{
firstName: 'matthew',
lastName: 'staniscia',
hairColor: 'brown',
age: 14
}
}
*/

Person(p1)
.map(objectMapper(makeUpper))
.chain(setContext)
.chain(agePerson(4))
.map(objectMapper(makeLower))
.chain(agePerson(4))
.map(objectMapper(makeCapitalize))

/*
{
map: [Function: map],
chain: [Function: chain],
isChild: false,
isTeen: false,
value:
{
firstName: 'Matthew',
lastName: 'Staniscia',
hairColor: 'Brown',
age: 18
}
}
*/
``````

Just for fun let’s include another Monad. We’ll use the Maybe monad from the Pratica library and add a function to see if that person can drink in the US.

``````import { Maybe } from 'pratica'

const maybeDrinkInUS = obj => (obj.age && obj.age >= 21 ? Maybe(obj) : Maybe())
``````

After running through the pipeline we’ll either return the data structure or a message.

``````Person(p1)
.map(objectMapper(makeUpper))
.chain(setContext)
.chain(agePerson(4))
.map(objectMapper(makeLower))
.chain(agePerson(4))
.map(objectMapper(makeCapitalize))
.chain(maybeDrinkInUS) // This returns a Maybe Monad
.cata({
Just: v => v,
Nothing: () => 'This Person is too young to drink in the US',
})

/*
Result
'This Person is too young to drink in the US'
*/

Person(p1)
.map(objectMapper(makeUpper))
.chain(setContext)
.chain(agePerson(4))
.map(objectMapper(makeLower))
.chain(agePerson(7)) // Changed this line to now be 21
.map(objectMapper(makeCapitalize))
.chain(maybeDrinkInUS) // This returns a Maybe Monad
.cata({
Just: v => v,
Nothing: () => 'This Person is too young to drink in the US',
})

/*
Result
{
firstName: 'Matthew',
lastName: 'Staniscia',
hairColor: 'Brown',
age: 21
}
*/
``````

## Conclusion

In conclusion a Monad is nothing more than a wrapper/context/class that has ability to:

• Map data within its own context.
• Chain by mapping over its data and extracting it from its context.
• Satisfies the 3 monadic laws.
• It might have extra properties or methods associated to it.

## Sources

The following links helped me understand Monads and be able to put it into words.

Akash Kava

I am not fan of these cool things, problem is implementation detail of code is left anywhere else, every time I see `agePerson(4)`, it does not tell me if it returns person with age less than 4 or more than 4 or equal to 4. This requires one to name the functions correctly which often leads to large names (have you seen names in Obj-c and Swift?).

And performance is far poor compared to plain verbose code, such huge chaining is problematic when you change one function and every chain fails.

These are best suitable for scientific calculations and fixed algorithms where names of every function/steps are well defined by some symbol or name which is constant. Such as `pi, min, max, avg, ...` etc. The underlying logic is deterministic.

No doubt this helps in reducing size of code, but at the same time, one must evaluate if it is worth and maintenance overhead needed in future.

They are good for Machine learning and other scientific applications, but really not good for long term changing business applications.

AVI

I'm not sure if that's a blanket perspective I can buy! Yes some of my variable names look like
makeSelectCheckoutStateWithTotals or isElementInViewportAndSticky but how is that bad if you ignore someone's personal preference or dogma or a habit of using short variable names.

Large apps have too much going on for one person to know or remember everything. Expressive variable names work as documentation. Also, to "age" means to grow older, but I would rely less on grammar and name it something like incrementAge.

About performance, it's valid if you write your code in a poor fashion where your compositions are just obfuscating everything for no reason. But if everything is safely composed and is type safe, I don't see any difference in a failure happening by composing something this way vs. a failure happening in the case where things are not composed. I have never been in a situation where chaining (through composition) has rendered me unable to debug a failure when one of my composed functions fails. It's in fact been easier to figure out what's wrong and fix it in my experience.

This post is a trivial example to help learn, but such compositional patterns in real software aren't used to add numbers to ages, but to isolate a lot of curried logic into its own separate bit of code. Which au contraire, makes your code more testable and less prone to failure. If something fails, you know exactly what failed instead of having to spend time figuring that out. Human error is bound to happen in any way you choose to program, but a functional approach makes it easier to pin point what's wrong due to granular separation of concerns.

Moreover, if your code is made with Justs and Nothings, the whole chain will short-circuit and none of the next functions will be called if any return a Nothing, so I really don't see any of your concerns as valid if they're coded by someone who have really embraced functional code and know what they're doing.

Only thing I'd change is using a function like 'pipe' or 'compose' to pass all these chained operators to. I'm not a big fan of .this().that().something() it's ugly in my eyes.

I use patterns like this for making web applications that thousands of people use every day, and these patterns aren't something that need to be applied to "scientific applications" only. You might wanna try Ramda on a small side-project or test app to experience it in real-world usage and see if your mind changes.

Akash Kava

I tried `Ramda` and I don't see the benefit of writing code differently, words like `thousands of people are doing it` doesn't mean everyone else should do it.

I am not saying it is bad, and again `scientific applications` was an example, not an absolute statement, you have to evaluate what is best in terms of maintainability, availability of talent and tooling, and whether application really requires functional stuff or not.

This is the biggest problem with developers today, to make things appear cool, they want to do it things differently and I fail to realize the point from business perspective.