kanskje
v2.0.2
Published
Simple Maybe monad written in TypeScript
Downloads
9
Readme
kanskje
Simple Maybe monad written in TypeScript
Table of Contents
Introduction
A Maybe represents a wrapper around any value - most commonly values that might be nullable. Having this wrapper is useful because it guards against null
and undefined
when doing computations on the value. The idea is that the Maybe deals with nullables internally, thus allowing one to write simpler code with fewer conditionals. Not until the very end when it's needed to unwrap the value again, is it necessary to deal with the fact that it was or somehow became nullable.
Maybe.fromNullable(getValueThatMightNotBeThere())
.map(doSomething)
.map(doAnotherThing)
.map(doFinalThing)
.getOrElse(defaultValue);
Even though they can't be constructed individually, the Maybe
consists of two classes: Just
and Nothing
. The Maybe
is a Just
if it holds a value and a Nothing
if it doesn't.
const toUpper = a => a.toUpperCase();
Maybe.fromNullable(['foo', 'bar', 'baz'][2]) // Just('baz')
.map(toUpper) // Just('BAZ')
.getOrElse('No value here');
// => 'BAZ'
Maybe.fromNullable(['foo', 'bar', 'baz'][3]) // Nothing
.map(toUpper) // Nothing
.getOrElse('No value here');
// => 'No value here'
There exists a number of great resources on Maybe monads - including The Marvellously Mysterious JavaScript Maybe Monad by @jrsinclair and Professor Frisby's Mostly Adequate Guide to Function Programming by @drboolean - that one might want to get familiar with. If you are used to Promises, you are essentially already familiar with monads.
Why kanskje?
kanskje stems from the need of a simple Maybe monad with type declarations making it usable for both JavaScript and TypeScript. The fact that the Maybe is written in TypeScript makes sure that it only contains methods that are actually possible to express using the TypeScript type system.
The source code is simple with one-line functions and no aliases, and yet the function declarations - using Generic Types of TypeScript - should be self-documenting.
Unlike some Maybe monads, kanskje doesn't perform behind the scenes conversions from Just
to Nothing
. As an example you can pass any mapper function, f: (a: A) => B
, to map
and be sure that the return type of f
isn't checked. A Just
is kept a Just
even if f
returns a nullable:
const unsafeProp = b => a => a[b];
Maybe.of({ name: 'Alice' }) // Just({ name: 'Alice' })
.map(unsafeProp('age')) // Just(undefined)
.getOrElse(25);
// => undefined
If Just(undefined)
is not the desired outcome, the mapper function, f
, needs to return a Maybe
and be passed to chain
instead:
const safeProp = b => a => Maybe.fromNullable(a[b]);
Maybe.of({ name: 'Alice' }) // Just({ name: 'Alice' })
.chain(safeProp('age')) // Nothing
.getOrElse(25);
// => 25
Usage
This is a CommonJS module.
Import the exported functions as named imports:
const { all, empty, fromNullable, of } = require('kanskje');
Or namespace them all under e.g. Maybe
:
const Maybe = require('kanskje');
Or use ES2015 module syntax if your environment supports that:
import { all, empty, fromNullable, of } from 'kanskje';
import * as Maybe from 'kanskje';
API
Constructing Maybe
s
To construct a Maybe
one of the following functions can be used:
all
Accepts an array or a tuple of Maybe
s and returns a single Maybe
. If all the Maybes were Just
s, the resulting Maybe
will be a Just
containing their values. If any of the Maybe
s where a Nothing
, the resulting Maybe
will be a Nothing
.
Signature:
all<A>(maybes: Maybe<A>[]): Maybe<A[]> all<A, B>(maybes: [Maybe<A>, Maybe<B>]): Maybe<[A, B]> all<A, B, C>(maybes: [Maybe<A>, Maybe<B>, Maybe<C>]): Maybe<[A, B, C]> all<A, B, C, D>( maybes: [Maybe<A>, Maybe<B>, Maybe<C>, Maybe<D>] ): Maybe<[A, B, C, D]> all<A, B, C, D, E>( maybes: [Maybe<A>, Maybe<B>, Maybe<C>, Maybe<D>, Maybe<E>] ): Maybe<[A, B, C, D, E]>
Example:
Maybe.all([Maybe.of('foo'), Maybe.of('bar'), Maybe.of('baz')]); // => Just(['foo', 'bar', 'baz'])
Maybe.all([Maybe.of('foo'[1]), Maybe.of('bar'[3]), Maybe.of('baz'[2])]); // => Just(['o', undefined, 'z'])
Maybe.all([ Maybe.of('foo'[1]), Maybe.fromNullable('bar'[3]), Maybe.of('baz'[2]), ]); // => Nothing
empty
Returns a Nothing
.
Signature:
empty<A>(): Maybe<A>
Example:
Maybe.empty(); // => Nothing
fromNullable
Lifts a value into a Maybe
but checks if the value is either null
or undefined
. If that is a case, a Nothing
is returned. Otherwise a Just
is returned.
Signature:
fromNullable<A>(a: Nullable<A>): Maybe<A>
Example:
Maybe.fromNullable('foo'); // => Just('foo')
Maybe.fromNullable(['foo', 'bar', 'baz'][3]); // => Nothing
of
Lifts a value into a Maybe
, more specifically: a Just
.
Signature:
of<A>(a: A): Maybe<A>
Example:
Maybe.of('foo'); // => Just('foo')
Maybe.of(['foo', 'bar', 'baz'][3]); // => Just(undefined)
Maybe
methods
Once a Maybe
is constructed, the following methods are accessible on the instance:
chain
Accepts a mapper function, f
, that returns a Maybe
and automatically unwraps the outer Maybe
to not end up with a Maybe<Maybe<B>>
.
Signature:
chain<B>(f: (a: A) => Maybe<B>): Maybe<B>
Example:
const safeHead = xs => Maybe.fromNullable(xs[0]); Maybe.of([1, 2, 3]).chain(safeHead); // => Just(1) Maybe.of([]).chain(safeHead); // => Nothing
filter
Accepts a predicate function, f
, and converts the Maybe
from a Just
to a Nothing
if its value does not adhere. If the Maybe
is already a Nothing
it remains a Nothing
.
Note: If used with TypeScript f
can be a Type Guard.
Signature:
filter(f: (a: A) => boolean): Maybe<A> filter<B extends A>(f: (a: A) => a is B): Maybe<B>
Examples:
Using a predicate function:
const isEven = a => a % 2 === 0; Maybe.of(4).filter(isEven); // => Just(4) Maybe.of(7).filter(isEven); // => Nothing
Using a TypeScript Type Guard:
interface Admin extends Person { password: string; } interface Person { name: string; } function isAdmin(a: Person): a is Admin { return a.hasOwnProperty('password'); } const carl: Admin = { name: 'Carl', password: '1234', }; const persons: Person[] = [ { name: 'Alice', }, carl, ]; Maybe.fromNullable(persons[0]).filter(isAdmin); // => Nothing Maybe.fromNullable(persons[1]).filter(isAdmin); // => Just({ name: 'Carl', password: '1234' })
fold
Accepts two functions: a mapper function, f
, and a function with the same return type, g
. If the Maybe
is a Just
its value is mapped and returned using f
. If it's a Nothing
the result of g
is returned.
Signature:
fold<B>(f: (a: A) => B, g: () => B): B
Example:
const unsafeProp = b => a => a[b]; const persons = [ { name: 'Alice', }, { name: 'Bob', }, ]; Maybe.fromNullable(persons[1]).fold( unsafeProp('name'), () => 'A person does not exist', ); // => 'Bob' Maybe.fromNullable(persons[2]).fold( unsafeProp('name'), () => 'A person does not exist', ); // => 'A person does not exist'
getOrElse
Accepts a default value, a
, and returns that if the Maybe
is a Nothing
. Otherwise the value of the Just
is returned.
Signature:
getOrElse(a: A): A
Example:
Maybe.fromNullable(getPortFromProcess()).getOrElse(3000);
isJust
Returns true
if the Maybe
is a Just
and false
if it's a Nothing
.
Signature:
isJust(): boolean
isNothing
Returns true
if the Maybe
is a Nothing
and false
if it's a Just
.
Signature:
isNothing(): boolean
map
Accepts any mapper function, f
, and applies it to the value of the Maybe
. If f
returns a Maybe
, the result will be a nested Maybe
.
Signature:
map<B>(f: (a: A) => B): Maybe<B>
Example:
const length = a => a.length; Maybe.of('foo').map(length); // => Just(3)
const safeHead = xs => Maybe.fromNullable(xs[0]); Maybe.of([1, 2, 3]).map(safeHead); // => Just(Just(1)) Maybe.of([]).map(safeHead); // => Just(Nothing)
orElse
Accepts a Maybe
, a
. If the instance Maybe
is a Nothing
it is replaced with a
. Otherwise it is left unchanged.
Signature:
orElse(a: Maybe<A>): Maybe<A>
Example:
const persons = [ { name: 'Alice', }, { name: 'Bob', }, ]; Maybe.fromNullable(persons[1]).orElse(Maybe.of({ name: 'Carl' })); // => Just('Bob') Maybe.fromNullable(persons[2]).orElse(Maybe.of({ name: 'Carl' })); // => Just('Carl')
unsafeGet
Unsafely unwraps the value from the Maybe
. Since Nothing
s don't contain a value, the function will throw an error if the Maybe
happened to be a Nothing
.
Signature:
unsafeGet(): A | void
Example:
Maybe.fromNullable(5).unsafeGet(); // => 5 Maybe.of(undefined).unsafeGet(); // => undefined Maybe.fromNullable(undefined).unsafeGet(); // => TypeError: A Nothing holds no value.