maybe-baby

Minimize defensive coding. A JavaScript implementation of the Maybe monad

npm version build status coverage status dependency status devDependency status

Install

  • $ npm install maybe-baby

Getting Started

What if we need the zipCode of the user below, which lives on the address object?

const user = { 
  email: 'foo@bar.com',
  address: null,
  name: {
     first: 'John',
     last: null,
     middle: null
  }
};

Accessing it via dot notation will result in an error:

const zipCode = user.address.zipCode;  // Uncaught TypeError: Cannot read property 'zipCode' of undefined

Possible Solutions?

  1. Write some ugly null checks that don't scale well:
function getZipCode(user) {
  if (user !== null && user !== undefined) {
     if (user.address !== null && user.address !== undefined) {
          return user.address.zipCode
      }
  }
}
  1. Use _.get() or something similar, but these libraries have large footprints, and most likely won't be implementing the monadic structure.

  2. Wait for optional chaining to be approved in ECMA, or use babel-plugin-transform-optional-chaining.

A Better Solution?

  1. Use maybe-baby to minimize defensive coding:
import Maybe from 'maybe-baby';

// Use a function getter
function getZipCode(user) {
  return Maybe.of(() => user.address.zipCode).join();
}

// Use prop getters
function getZipCode(user) {
  return Maybe.of(user)
    .prop('address')
    .prop('zipCode')
    .join();
}

Now we can safely get the zipCode without worrying about the shape of the object, or encountering TypeErrors:

const zipCode = getZipCode(user);
console.log(zipCode);  // undefined

Docs

Documentation generated via JSDoc.


API

Check out the API below, or the complete documentation.

of(val|func)

Accepts a value of any type, and returns a monad:

const str = Maybe.of('foo');
const num = Maybe.of(123);
const bool = Maybe.of(true);
const obj = Maybe.of({});
const arr = Maybe.of([]);
const empty = Maybe.of(null);
const undef = Maybe.of(undefined);

Accepts a function, and sets the function's return value as the monad's value, returns a monad.

If the function results in an error, the monad's value is set to undefined.

const user = {};
const mZipCode = Maybe.of(() => user.address.zipCode);

console.log(mZipCode.join()); // undefined

isJust()

Returns true if the value is not null or undefined:

Maybe.of(123).isJust();   // true
Maybe.of(null).isJust();  // false

isNothing()

Returns true if the value is null or undefined:

Maybe.of(123).isNothing();   // false
Maybe.of(null).isNothing();  // true

join()

Returns the value:

Maybe.of(123).join();   // 123
Maybe.of(null).join();  // null

orElse(val)

Chain to the end of a monad to return as the default value if isNothing() is true:

Maybe.of(undefined)
  .orElse('No Value')
  .join();  // 'No Value'

prop(<string|number>)

Use prop to get values at arbitrary depths.

Accepts a single string argument.

const obj = Maybe.of({ 
  foo: { 
    bar: [123, 456] 
  } 
});

obj.prop("foo").join(); // { bar: [123, 456] }

obj
  .prop("foo")
  .prop("bar")
  .join();  // [123, 456]

obj
  .prop("foo")
  .prop("bar")
  .prop(1)
  .join();  // 456

path(<string>)

Use path to get values at arbitrary depths.

Accepts a period-delimited string.

const obj = Maybe.of({ 
  foo: { 
    bar: [123, 456] 
  } 
});

obj.path('foo').join();        // { bar: [123, 456] }
obj.path('foo.bar').join();    // [123, 456]
obj.path('foo.bar.1').join();  // 456

map(func)

Apply a transformation to the monad, and return a new monad:

const val = 1;
const newVal = Maybe.of(val).map(val => val + 1);

newVal.join(); // 2;

chain(func)

Chain together functions that return Maybe monads:

function addOne (val) {
  return Maybe.of(val + 1);
}

const three = Maybe.of(1)
 .chain(addOne)
 .chain(addOne);

 three.join(); // 3

Credit

Credit to James Sinclair for writing The Marvellously Mysterious JavaScript Maybe Monad.