monady
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);
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'));
const result1 = rejectWhenNothing(5).then(
val => val + 3,
err => assert.ifError(err));
assert.equal(result1, 8);
const result2 = rejectWhenNothing(maybe(5)).then(
val => val + 3,
err => assert.ifError(err));
assert.equal(result2, 8);
rejectWhenNothing(nothing).then(
() => assert(false),
err => assert(err instanceof Error)
);
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 ).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;
const rejectWhenNothing = RejectWhen.lift(
val => val === nothing,
() => new Error('value rejected'),
maybe);
const user = yield rejectWhenNothing(userProvider.findOne({
name: req.body.username
});
}
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'));
const getUser = R.compose(
rejectWhenNothing,
maybe,
(a) => userProvider.findOne(a));
const user = yield getUser({ name: req.body.username });
}
License
MIT