Skip to main content

Monads

Out of the variety of monads fnts chooses two presumably most suitable ones: maybe and either. Each have their own constructors and operators (as opposed to classes and methods based approach seen commonly).

Maybe

maybe constructor creates an instance of the Maybe monad. A nullable value (undefined or null) produces Nothing, any other value – Just. Each one is just a POJO with a single key – a unique identifier for the instance, by which it is recognised by some of the operators.

import maybe from 'fnts/maybe';

const maybeNumber = maybe([1, 2, 3].find((n) => n > 2));

The maybeNumber variable here has a type Maybe<number> and holds a value of either just or nothing.

Operators

There'd be no much use to it if we could still not perform any operations on maybeNumber. That's where operators come into play.

An operator is a special function that accepts an instance of the maybe monad and assumes an underlying value (a number in this case) to always be "real", throwing undefined or null away.

This way we can operate on it without having to worry about if it's truthy or not every time we have to do something with it.

In FP, the distinctive feature of a maybe monad is the bind operation, which acts similarly to Promise.then combining the accepted monad and the newly retuned one into a single Maybe:

import maybe from 'fnts/maybe';
import { bind } from 'fnts/maybe/operators';

bind(
maybeNumber,
(n) => maybe(n * n)
); // Maybe<number>

Another common example of an operator for Maybe is fmap, which maps its value onto the new one and returns it wrapped into the monad again:

import { fmap } from 'fnts/maybe/operators';

const maybeNumberIncremented = fmap(
maybeNumber,
(n) => n + 1
);

To get rid of the Maybe wrapping we utilise the fold operator:

import { fold } from 'fnts/maybe/operators';

const numberIncremented = fold(maybeNumberIncremented);

A nice thing about fold is that it knows which specific constructor it received, and thus can infer the return type based on that information. Here, we can have numberIncremented to be of type number as long as we're sure the maybeNumberIncremented monad was just a Just.

Either

Another cool monad in fnts is Either. It works basically the same as Maybe, having two constructors – Left and Right and a set of operators.

The either function accepts an asynchronous function in its argument, successful result of which becomes Right, and the thrown error – Left,

import tap from 'fnts/tap';
import either from 'fnts/either';
import identity from 'fnts/identity';
import { bifoldMap } from 'fnts/either/operators';

bifoldMap(
await either(
() => fetch('https://github.com')
),
tap(console.error),
identity
)

Synchronous either

For safely performing synchronous tasks that still may fail with an error, there is the eitherSync function:

import eitherSync from 'fnts/either';
import { bifoldMap } from 'fnts/either/operators';

bifoldMap(
eitherSync(
() => JSON.parse(localStorage.getItem('context'))
),
(error) => console.error(error),
(data) => data
);

Guards

Every monad instance can be validated through the pre-defined type guard functions: isJust, isNothing, isLeft, isRight.

import maybe from 'fnts/maybe';
import { isJust, fold } from 'fnts/maybe/operators';

isJust(maybeNumber)
? calculate(fold(maybeNumber)) // Just<number>
: 0;