Skip to content

Instantly share code, notes, and snippets.

@sukima
Last active December 30, 2015 19:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sukima/d117a0223a9884f2c860 to your computer and use it in GitHub Desktop.
Save sukima/d117a0223a9884f2c860 to your computer and use it in GitHub Desktop.
Simple Maybe monad utility for dealing with lookup on objects, etc. (1.8kb minified)
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.Maybe = factory();
}
}(this, function(undefined) {
// Maybe monad can be created with or without the new keyword.
// This wraps an object and allows a deep selector (optional) and a default
// value (optional). It is safe to wrap another Maybe object.
//
// var example = { foo: { bar: { baz: 'foobarbaz' } } };
// var x = new Maybe(example, 'foo.bar.baz'); // new is optional
// var y = Maybe(example, 'foo.nosuchthing.baz');
// x.value() // => 'foobarbaz'
// y.value() // => null
//
// @param obj - the original value. Can be anything.
// @param selctor - (optional) used to safely pick a deep value from obj.
// @param def - (optional) a default value if obj or any level of the
// selector resolves to nothing.
//
function Maybe(obj, selector, def) {
if (!(this instanceof Maybe)) { return new Maybe(obj, selector, def); }
if (obj instanceof Maybe) { return new Maybe(obj._value, selector, def); }
this._value = Maybe.safeRead(obj, selector);
this.setDefaultValue(def);
}
// Check if the value resolves to nothing.
// @returns boolean
//
Maybe.prototype.isNothing = function() {
return (this._value == null);
};
// Sets the default returned from value() when this Maybe is nothing.
//
// Maybe(null)
// .setDefaultValue('foobar')
// .value(); // => 'foobar'
//
// @param def - a default value
// @returns this Maybe object (chainable)
//
Maybe.prototype.setDefaultValue = function(def) {
this._default = (def != null) ? def : null;
return this;
};
// Convert the Maybe to a resolved value. Either the value or the deafult if
// this Maybe is nothing.
// @param def - (optional) use as default value overridding any previous
// defaults.
// @returns a value, the default value, or null
//
Maybe.prototype.value = function(def) {
return !this.isNothing() ? this._value :
(def != null ? def : this._default);
};
// Perform a transformation or action unless the value is nothing. This is
// the main way to interface with a Maybe. The functions' return value will
// be the new value propagated through the chain. Returning undefined (a
// function with no return value) does not mutate the previous value (no-op).
// Return null if you want to the Maybe to be nothing.
//
// Maybe('foo')
// .bind(function(v) { return v + 'bar'; })
// .bind(function(v) { console.log(v); }) // foobar
// .bind(function(v) { return v + 'baz'; })
// .value(); // => 'foobarbaz'
//
// Maybe('foo')
// .bind(function(v) { return null; })
// .bind(function(v) { return v + 'baz'; })
// .value(); // => null
//
// @param fn - a function to execute if this Maybe is not nothing.
// @returns a Maybe object
//
Maybe.prototype.bind = function(fn) {
if (this.isNothing()) { return this; }
var ret = fn.call(this, this._value);
if (ret === undefined) { return this; }
return new Maybe(ret, null, this._default);
};
// Execute/mutate with the function if this Maybe is nothing.
//
// Maybe(null)
// .bind(function(v) { return v + 'foo'; })
// .nothing(function() { return 'bar'; })
// .bind(function(v) { return v + 'foo'; })
// .value(); // => 'barfoo'
//
// @param fn - a function to execute if this Maybe is nothing.
// @returns a Maybe object
//
Maybe.prototype.nothing = function(fn) {
if (!this.isNothing()) { return this; }
return new Maybe(fn(), null, this._default);
};
// Helper to return a selector from a value object.
//
// Maybe({ foo: { bar: 'baz' } })
// .get('foo.bar')
// .value(); // 'baz'
//
// @param selector - the property selector to get from the value.
// @returns a Maybe object
//
Maybe.prototype.get = function(selector) {
return new Maybe(this, selector, this._default);
};
// Invoke a method with args on the object if Maybe is not nothing.
//
// Maybe(['foo', 'bar', 'baz'])
// .invoke('join', ', ')
// .value(); // => 'foo, bar, baz'
//
// @param selector - the property selector to get from the value.
// @returns a Maybe object
//
Maybe.prototype.invoke = function(fnName) {
if (this.isNothing()) { return this; }
var fn = this.get(fnName).value(null);
var args = (2 <= arguments.length) ? [].slice.call(arguments, 1) : [];
var ret = (fn == null) ? null : fn.apply(this.value_, args);
if (ret === undefined) { return this; }
return new Maybe(ret, null, this._default);
};
// Coerce the value to a string.
//
// Maybe([1, 2, 3])
// .toString(); // => '1,2,3'
//
// @param def - (optional) use as default value overridding any previous
// @returns a String
//
Maybe.prototype.toString = function(def) {
return '' + this.value(def || this._default || '');
};
// Coerce the value to JSON.
//
// Maybe({ foo: { bar: 'baz' } })
// .toJSON(); // => '{foo:{bar:"baz"}}'
// Maybe(null)
// .toJSON(); // => '{}'
//
// @param def - (optional) use as default value overridding any previous
// @param replacer - (optional) See JSON.stringify()
// @param space - (optional) See JSON.stringify()
// @returns a JSON encoded String
//
Maybe.prototype.toJSON = function(def, replacer, space) {
var val = this.value(def || this._default || {});
return JSON.stringify(val, replacer, space);
};
// A utility function to safely recurse through an object based on a selector.
// if any value in the chain resolves to nothing then this will simple return
// null. Used internally to look up values when constructing a Maybe object.
//
// var obj = { foo: { bar: { baz: 'foobar' } } };
// Maybe.safeRead(obj, 'foo.bar.baz'); // => 'foobar'
//
// @param obj - the object to traverse.
// @param selector - the selector to pick from the obj.
// @returns any value or null.
//
Maybe.safeRead = function(obj, selector) {
if (obj == null) { return null; }
if (!selector || selector.length === 0) { return obj; }
if ('string' === typeof selector) {
selector = selector.split('.');
}
return Maybe.safeRead(obj[selector.shift()], selector);
};
return Maybe;
}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment