Check the third part of our Power of functional programming in JavaScript article series. This time our JavaScript expert explains more about Functor and Monad Maybe.
Introduction
Often keeping unmodifiable is difficult to maintain. The pattern of wrapping data into a container comes to the rescue. It secures the values so that handling them is safe with the exclusion of side effects.
If you are new here make sure to check my last 2 parts regarding functional programming on The Codest blog about:
Functor
It has no complex logic. Its main task is to wrap the context and perform the functions it receives from the outside on them. Each time the value changes, a new container instance is repackaged and returned. When calling the map method, which takes a specific action, it returns a new container instance with the value returned by the passed function, while maintaining the non-modifiable principle.
Declaration
const Functor = value => ({
map: fn => Functor(fn(value)),
chain: fn => fn(value),
of: () => value
});
map – useful when you want to change the state of a value in a container but don’t want to return it yet.
chain – used if you want to pass a value to a function without modifying the container state. Usually at the end of map calls.
of – return current value
Imperative example
const randomInt = (max) => Math.floor(Math.random() * (max + 1))
const randomNumber = randomInt(200) // returns number between 0 and 200
decrease(randomNumber) // returns (number between 0 and 200) - 1
Declarative example
const randomIntWrapper = (max) =>
Functor(max)
.map(increase) // max + 1
.map(multiplyBy(Math.random())) // Math.random() * (max + 1)
.map(Math.floor) // Math.floor(Math.random() * (max + 1))
const randomNumber = randomIntWrapper(200)
randomNumber.of() // returns number between 0 and 200
randomNumber.chain(decrease) // returns (number between 0 and 200) - 1
Monad
Sometimes, in addition to the functions that trigger the new state of the value, you need additional logic hidden in the container. This is where monad comes in handy, as it is an extension of functor. It can, for example, decide what should happen when the value has a certain value or what path the next actions are to take.
Monad Maybe
Monad maybe solves the problem of values that return not true. When this happens, subsequent map calls are ignored, but it allows you to return an alternative by calling the getOr method. It allows you to avoid the use of if / else operators, which are popular in imperative programming. This monad consists of three containers:
Nothing – runs when a value that is not true falls into the container or filter method returns false. It is used to simulate the execution of a function. This means that this container receives the function but does not execute it.
Just – this is the main container that performs all the functions, but if the value changes to a false value or filter method returns false, it will pass it to the Nothing container.
Maybe – I take the initial value and decide what container to call at the start.
Declaration
const Just = value => ({
map: fn => Maybe(fn(value)),
chain: fn => fn(value),
of: () => value,
getOr: () => value,
filter: fn => fn(value) ? Just(value) : Nothing(),
type: 'just'
});
const Nothing = () => ({
map: fn => Nothing(),
chain: fn => fn(),
of: () => Nothing(),
getOr: substitute => substitute,
filter: () => Nothing(),
type: 'nothing'
});
const Maybe = value =>
value === null || value === undefined || value.type === 'nothing'
? Nothing()
: Just(value)
Now let’s build the previous example about the condition. If max is greater than zero, the function will be executed. Otherwise, it will return 0.
Imperative example
const randomInt = (max) => {
if(max > 0) {
return Math.floor(Math.random() * (max + 1))
} else {
return 0
}
}
const bookMiddlePage = 200
const randomPage = randomInt(10) || bookMiddlePage // returns random
const randomPage = randomInt(-10) || bookMiddlePage // returns 200
Declarative example
const randomIntWrapper = (max) =>
Maybe(max)
.filter(max => max > 0) // value false ignores further calls
.map(increase)
.map(multiplyBy(Math.random()))
.map(Math.floor)
const bookMiddlePage = 200
// Just container
const randomPage = randomIntWrapper(10).getOr(bookMiddlePage) // returns random
// Nothing container
const randomPage = randomIntWrapper(-10).getOr(bookMiddlePage) // returns 200
