Software Development
Pawel Ged
Pawel Ged
Vue.js Developer
2022-06-21

Power of functional programming in JavaScript Part 3 - Functor & Monad Maybe

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)

table methods functor

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

Read more:

Pros and Cons of Vue

Data fetching strategies in NextJS

React: Tips and Tricks