monady

0.3.0 • Public • Published

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 but can be bound, lifted or mapped.

maybe(x) type constructor

just(x)

nothing

assert(maybe(5) instanceof Just);
assert.equal(maybe(5), 5);
assert.equal(just(5), 5);
 
assert(maybe(nothing) instanceof Nothing);
assert.equal(maybe(nothing), nothing);
 
assert(maybe(null) instanceof Nothing);
assert.equal(maybe(null), nothing);
 
assert(maybe() instanceof Nothing);
assert.equal(maybe(), nothing);

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

assert.equal(just(5).then(a => a + 3), 8);
 
assert.strictEqual(nothing.bind(), nothing);
 
// nothing is not thenable
assert.throws(() => nothing.then(a => a), TypeError);

lift(x => )

const lifted1 = Just.lift(x => x + 3);
assert(lifted1(5) instanceof Just);
assert.equal(lifted1(5), 8);
 
assert(just(5).then(lifted1) instanceof Just);
assert.equal(just(5).then(lifted1), 8);
 
const lifted2 = Nothing.lift(x => x + 3);
assert(lifted2(5) instanceof Nothing);
assert.equal(lifted2(5), nothing);
 
assert(just(5).then(lifted2) instanceof Nothing);
assert.equal(just(5).then(lifted2), nothing);

lift2((x, y) => )

const lifted1 = Just.lift2((x, y) => x + y);
assert(lifted1(5, 3) instanceof Just);
assert.equal(lifted1(5, 3), 8);
 
const lifted2 = Nothing.lift2((x, y) => x + y);
assert(lifted2(5, 3) instanceof Nothing);
assert.equal(lifted2(5, 3), nothing);

map(x => )

assert(just(5).map(a => a + 3) instanceof Just);
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);

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

Package Sidebar

Install

npm i monady

Weekly Downloads

0

Version

0.3.0

License

MIT

Last publish

Collaborators

  • georgesapkin