Monas (from Greek μονάς - "singularity") is a monad library for JavaScript apps. It's been inspired by Scala and developed with TypeScript. Monas introduces two fundamental monads: Option<A>
and Either<A, B>
.
$ yarn add monas
Represents optional values. Instances of Option are either an instance of Some
or the object None
.
The idea is to get rid of null
and undefined
and, thus, eliminate null pointer exceptions, reduce branching (if statement) and produce better code.
 Exported                                     | Description |
---|---|
Option<A> |
the base abstract class that implements utility functions for instances of classes Some and None . It's primarily used to indicate that a type has an optional value. |
Some<A> |
one of the possible implementations of Option<A> that wraps a value. The incapsulated value is available by the get() method. |
None<A> |
another implementation of Option<A> indicating absence of value. |
some<A>(x: A): Option<A> |
a helper function instantiating objects of type Some or None based on a provided value. |
none: Option<A> |
a single instance of None<A> . |
let greeting: Option<string> = some('Hello world');
greeting = some(null); // will be none
assert(greeting === none);
some()
will return none
if a given argument is null
or undefined
:
Reading data from Option
:
a) getOrElse()
:
let str: string = greeting.getOrElse('');
// Returns greeting or empty string if none.
b) get()
:
let str: string = greeting.get();
// Returns greeting or throws an exception.
c) Using Symbol.Iterable
:
let [str]: string = [...greeting];
// returns ["something"] or empty array [] if none.
OR
for(let str: string of greeting) {
assert(str, "something");
}
The most idiomatic way to use an Option
instance is to treat it as a collection or monad and use map, flatMap, filter, or foreach.
Let's consider an example where for a given country code we need to find the country name or print "N/A" if it's not found.
import { Option, none, some } from './Option';
type Country = { name: string, code: number };
let countries: Country[] = [{ name: 'United States', code: 1 }, { name: 'United Kingdom', code: 44 }];
function getNameByCode(code: number): string {
// find a country by code
const country = countries.find(c => c.code === code);
// if the country is not null return the name or N/A
return some(country).map(_ => _.name).getOrElse('N/A');
// ^^^ ^^^ select name ^^^ get a value if exists
// create Option<Country> otherwise use 'N/A'
}
More examples could be found here.
Represents a value of one of two possible types (a disjoint union).
An instance of Either is an instance of either Left
or Right
.
Convention dictates that Left is used for failure and Right is used for success.
 Exported                                                   | Description |
---|---|
Either<A, B> |
the base abstract class that implements utility functions for instances of classes Left and Right . |
Right<A, B> |
a right "good" part. |
Left<A, B> |
a left "fail over" part, e.g. Error. |
right<A, B>(x: B): Either<A, B> |
a helper function instantiating Right objects. |
left<A, B>(x: B): Either<A, B> |
a helper function instantiating Left objects. |
Generally Either
can be considered as an alternative to Option
where instead of
None
a useful information could be encapsulated into Left
.
It turns out that Either
is a power-full type for validations
since it can return either a successfully parsed value, or a validation error.
Either<A, B>
can be instantiated by calling right(something)
or left(error)
:
let eitherNumber: Either<Error, number> = right(42); // equivalent to new Right(42)
OR
let eitherNumber: Either<Error, number> = left(Error('Not a number')); // equivalent to new Left('Not a number')
Either
is a right-biased monad:
let eitherNum: Either<number, Error> = right<number, Error>(42);
eitherNum.map(num => num * 2).getOrElse(-1);
// returns 84
Another example:
let eitherNum: Either<number, Error> = left<number, Error>(Error('Not a number.'));
eitherNum.map(_ => _ * 2).getOrElse(-1);
// returns -1
Use mapLeft()
, foreachLeft()
to handle Left
values:
function print(numberOrError: Either<Error, number>) {
numberOrError
.map(num => num)
.foreach(printNumber)
.mapLeft(err => err.message)
.foreachLeft(printError);
}
Use swap()
function to swap Left
and Right
.
left<number, string>('Not a number').swap().getOrElse('');
// returns 'Not a number'
Either
implements Symbol.Iterable
:
let eitherNumber = right<number, Error>(42).map(_ => _ * 2);
let [n] = [...eitherNumber]; // n === 42
for(let num of eitherNumber) {
assert(num, 42);
}
More examples could be found here.
MIT