Home

Awesome

monady

NPM version Build status Test coverage Downloads

Composable monads for functional async flow.

Name

It's like Monday, but misspelled :) Also, apparently, it's Polish for monads.

Installation

$ npm install monady

Monads

Identity

Identity always has a value.

identity(x) type constructor

assert.equal(identity(5), 5);
assert.throws(() => identity());
assert.throws(() => identity(null));

bind(x => ) or then(x => )

assert.equal(identity(5).then(a => a + 3), 8);

lift(x => )

const lifted = Identity.lift(x => x + 3);
assert(lifted(5) instanceof Identity);
assert.equal(lifted(5), 8);

assert(identity(5).then(lifted) instanceof Identity);
assert.equal(identity(5).then(lifted), 8);

lift2((x, y) => )

const lifted = Identity.lift2((x, y) => x + y);
assert(lifted(5, 3) instanceof Identity);
assert.equal(lifted(5, 3), 8);

map(x => )

assert(identity(5).map(a => a + 3) instanceof Identity);
assert.equal(identity(5).map(a => a + 3), 8);

Maybe, Just and Nothing

maybe resolves to either just or nothing depending on whether a value is passed to the type constructor. nothing is not thenable.

maybe(x) type constructor

just(x)

nothing

assert(maybe(5).isJust);
assert.equal(maybe(5), 5);
assert.equal(just(5), 5);

assert(maybe(nothing).isNothing);
assert.equal(maybe(nothing), nothing);

assert(maybe(null).isNothing);
assert.equal(maybe(null), nothing);

assert(maybe().isNothing);
assert.equal(maybe(), nothing);

bind(x => ) or then(x => )

assert.equal(just(5).then(a => a + 3), 8);

assert.strictEqual(nothing.bind(a => a + 3), nothing);

// nothing is not thenable
assert.throws(() => nothing.then(a => a), TypeError);

lift(x => )

const lifted1 = Maybe.lift(x => x + 3);
assert(lifted1(5).isJust);
assert.equal(lifted1(5), 8);

assert(just(5).then(lifted1).isJust);
assert.equal(just(5).then(lifted1), 8);

lift2((x, y) => )

const lifted1 = Maybe.lift2((x, y) => x + y);
assert(lifted1(5, 3).isJust);
assert.equal(lifted1(5, 3), 8);

map(x => )

assert(just(5).map(a => a + 3).isJust);
assert.equal(just(5).map(a => a + 3), 8);

assert.equal(nothing.map(a => a), nothing);

Either

Either returns either left (default) or right, if it is set.

either(left, right) type constructor

assert.equal(either(5), 5);
assert.equal(either(5, 7), 7);
assert.throws(() => either());

bind(x => ) or then(x => )

assert.equal(either(5).then(a => a + 3), 8);
assert.equal(either(5, 7).then(a => a + 3), 10);

lift(x => )

const lifted = Either.lift(x => x + 3);
assert(lifted(5) instanceof Either);
assert.equal(lifted(5), 8);

assert(either(5).then(lifted) instanceof Either);
assert.equal(either(5).then(lifted), 8);

assert(either(5, 7).then(lifted) instanceof Either);
assert.equal(either(5, 7).then(lifted), 10);

lift2((x, y) => )

const lifted = Either.lift2((x, y) => x + y);
assert(lifted(5, 3) instanceof Either);
assert.equal(lifted(5, 3), 8);

map(x => )

assert(either(5).map(a => a + 3) instanceof Either);
assert.equal(either(5).map(a => a + 3), 8);

assert(either(5, 7).map(a => a + 3) instanceof Either);
assert.equal(either(5, 7).map(a => a + 3), 10);

List

List represents computations which may return 0, 1, or more possible results. List is not thenable.

list(...args) type constructor

assert.deepEqual(list(5), [5]);
assert.deepEqual(list(5, 7, 11), [5, 7, 11]);
assert.deepEqual(list(), []);

bind(x => )

assert.deepEqual(list(5, 7, 11).bind(a => a + 3), [8, 10, 14]);

lift(x => )

const lifted = List.lift(x => x + 3);
assert(lifted(5) instanceof List);
assert.equal(lifted(5), 8);

assert(list(5).then(lifted) instanceof List);
assert.equal(list(5).then(lifted), 8);

lift2((x, y) => )

const lifted = List.lift2((x, y) => x + y);
assert(lifted(5, 3) instanceof List);
assert.equal(lifted(5, 3), 8);

map(x => )

assert(list(5, 7).map(a => a + 3) instanceof List);
assert.deepEqual(list(5, 7).map(a => a + 3), [8, 10]);

RejectWhen

RejectWhen rejects a value on bind (or then) with error when condition when is met.

rejectWhen(when, error, value) type constructor

bind(x => ) or then(x => )

const rejectWhenNothing = rejectWhen.bind(null,
    val => val === nothing,
    ()  => new Error('value rejected'));

// resolves value
const result1 = rejectWhenNothing(5).then(
    val => val + 3,
    err => assert.ifError(err));
assert.equal(result1, 8);

// resolve maybe value
const result2 = rejectWhenNothing(maybe(5)).then(
    val => val + 3,
    err => assert.ifError(err));
assert.equal(result2, 8);

// rejects nothing
rejectWhenNothing(nothing).then(
    () => assert(false),
    err => assert(err instanceof Error)
);

// rejects maybe nothing
rejectWhenNothing(maybe(nothing)).then(
    () => assert(false),
    err => assert(err instanceof Error));

lift(x => )

const lifted = RejectWhen.lift(() => {}, () => {}, x => x + 3);
assert(lifted(5) instanceof RejectWhen);
assert.equal(lifted(5), 8);

lift2((x, y) => )

const lifted = RejectWhen.lift2(() => {}, () => {}, (x, y) => x + y);
assert(lifted(5, 3) instanceof RejectWhen);
assert.equal(lifted(5, 3), 8);

map(x => )

assert(rejectWhenNothing(5).map(val => val + 2) instanceof RejectWhen);
assert.equal(rejectWhenNothing(5).map(val => val + 2), 7);

Examples

Rejecting value when it is Nothing using RejectWhen and maybe.

const rejectWhenNothing = RejectWhen.lift(
    val => val === nothing,
    () => new Error('value rejected'),
    maybe);

const result = rejectWhenNothing(5).then(
    val => val + 3,
    err => assert.ifError(err));
assert.equal(result, 8);

rejectWhenNothing(null /* or nothing or not specified */).then(
    () => assert(false),
    err => assert(err instanceof Error));

Rejecting value when it is not set using RejectWhen and either.

const rejectWhenError = RejectWhen.lift2(
    val => val.value instanceof Error,
    err => err.value,
    either);

const result = rejectWhenError(new Error(), 5).then(
    val => val + 3,
    err => assert.ifError(err));
assert.equal(result, 8);

rejectWhenError(new Error()).then(
    () => assert(false),
    err => assert(err instanceof Error));

Using generator functions and lifting to avoid explicit null checks.

function* (next) {
    const req = this.request;

    // Lift Maybe into RejectWhen context
    const rejectWhenNothing = RejectWhen.lift(
        val => val === nothing,
        () => new Error('value rejected'),
        maybe);

    const user = yield rejectWhenNothing(userProvider.findOne({
        name: req.body.username
    });

    // If user is not found Maybe will return nothing and be rejected by
    // RejectWhen so code after yield will not run. Hence no check if user
    // exists is needed.

    // Do something with user
}

Using generator functions with ramda for compose and curry to avoid explicit null checks.

function* (next) {
    const req = this.request;

    const rejectWhenNothing = R.curry(rejectWhen)(
        val => val === nothing,
        () => new Error('value rejected'));

    // maybe wraps Promise from findOne and is in turn wrapped in
    // rejectWhenNothing.
    const getUser = R.compose(
        rejectWhenNothing,
        maybe,
        (a) => userProvider.findOne(a));

    const user = yield getUser({ name: req.body.username });

    // If user is not found Maybe will return nothing and be rejected by
    // RejectWhen so code after yield will not run. Hence no check if user
    // exists is needed.

    // Do something with user
}

License

MIT