Source Code Examples

Coffee Chart35

Object Recipes

proxy

function proxy (baseObject) {
  var proxyObject = Object.create(null),
      methodName;
  for (methodName in baseObject) {
    if (typeof(baseObject[methodName]) ===  'function') {
      (function (methodName) {
        proxyObject[methodName] = function () {
          var result = baseObject[methodName].apply(baseObject, arguments);
          return (result === baseObject)
                 ? proxyObject
                 : result;
        }
      })(methodName);
    }
  }
  return proxyObject;
}

record

function Record (template) {
  if (Record.prototype.isPrototypeOf(this)) {
    var struct = this;

    Object.keys(template).forEach(function (key) {
      Object.defineProperty(struct, key, {
        enumerable: true,
        writable: true,
        value: template[key]
      });
    });
    return Object.preventExtensions(struct);
  }
  else return new Record(template);
}

value

var Value = (function () {

  function Value (template) {

    if (Value.prototype.isPrototypeOf(this)) {
      var immutableObject = this;

      Object.keys(template).forEach(function (key) {
        Object.defineProperty(immutableObject, key, {
          enumerable: true,
          writable: false,
          value: template[key]
        });
      });
      return Object.preventExtensions(immutableObject);
    }
    else return new Value(template);
  }

  Value.prototype = new Record({});

	function eqv (a, b) {
		var akeys, bkeys;

		if (a === b) {
			return true;
		}
		else if (a instanceof Value && b instanceof Value){
			akeys = Object.keys(a);
			bkeys = Object.keys(b);

			if (akeys.length !== bkeys.length) {
				return false;
			}
			else return akeys.every(function (key) {
				return eqv(a[key], b[key]);
			});
		}
		else return false;
	}

	Value.eqv = eqv;
	Value.prototype.eqv = function (that) {
		return eqv(this, that);
	};

	return Value;

})();

Methods

when

function equals (x) {
  return function eq (y) { return (x === y); };
}

function not (fn) {
  var name = fn.name === ''
             ? "not"
             : "not_" + fn.name;

  return nameAndLength(name, fn.length, function () {
    return !fn.apply(this, arguments)
  });
}

function getWith (prop, obj) {
  function gets (obj) {
    return obj[prop];
  }

  return obj === undefined
         ? gets
         : gets(obj);
}

function mapWith (fn, mappable) {
  function maps (collection) {
    return collection.map(fn);
  }

  return mappable === undefined
         ? maps
         : maps(collection);
}

function pluckWith (prop, collection) {
  var plucker = mapWith(getWith(prop));

  return collection === undefined
         ? plucker
         : plucker(collection);
}

function nameAndLength(name, length, body) {
  var abcs = [ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',
               'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',
               'z', 'x', 'c', 'v', 'b', 'n', 'm' ],
      pars = abcs.slice(0, length),
      src  = "(function " + name + " (" + pars.join(',') + ") { return body.appl\
y(this, arguments); })";

  return eval(src);
}

function imitate(exemplar, body) {
  return nameAndLength(exemplar.name, exemplar.length, body);
}

function when (guardFn, optionalFn) {
  function guarded (fn) {
    return imitate(fn, function () {
      if (guardFn.apply(this, arguments))
        return fn.apply(this, arguments);
    });
  }
  return optionalFn == null
         ? guarded
         : guarded(optionalFn);
}

match

function Match () {
  var fns     = [].slice.call(arguments, 0),
      lengths = pluckWith('length', fns),
      length  = Math.min.apply(null, lengths),
      names   = pluckWith('name', fns).filter(function (name) { return name !== \
''; }),
      name    = names.length === 0
                ? ''
                : names[0];

  return nameAndLength(name, length, function () {
    var i,
        value;

    for (i in fns) {
      value = fns[i].apply(this, arguments);

      if (value !== undefined) return value;
    }
  });
}

function isType (type) {
  return function (arg) {
    return typeof(arg) === type;
  };
}

function instanceOf (clazz) {
  return function (arg) {
    return arg instanceof clazz;
  };
}

function isPrototypeOf (proto) {
  return Object.prototype.isPrototypeOf.bind(proto);
}

when-args-are

function whenArgsAre () {
  var matchers = [].slice.call(arguments, 0, arguments.length - 1),
      body     = arguments[arguments.length - 1];

  function typeChecked () {
    var i,
        arg,
        value;

    if (arguments.length != matchers.length) return;
    for (i in arguments) {
      arg = arguments[i];
      if (!matchers[i].call(this, arg)) return;
    }
    value = body.apply(this, arguments);
    return value === undefined
           ? null
           : value;
  }

  return imitate(body, typeChecked);
}

Metaobjects

extend-privately

function extendPrivately (receiver, mixin) {
  var methodName,
      privateProperty = Object.create(null);

  for (methodName in mixin) {
    if (mixin.hasOwnProperty(methodName)) {
      receiver[methodName] = mixin[methodName].bind(privateProperty);
    };
  };
  return receiver;
};

forward

function forward (receiver, metaobject, methods) {
  if (methods == null) {
    methods = Object.keys(metaobject).filter(function (methodName) {
      return typeof(metaobject[methodName]) == 'function';
    });
  }
  methods.forEach(function (methodName) {
    receiver[methodName] = function () {
      var result = metaobject[methodName].apply(metaobject, arguments);
      return result === metaobject ? this : result;
    };
  });

  return receiver;
};

delegate

function delegate (receiver, metaobject, methods) {
  if (methods == null) {
    methods = Object.keys(metaobject).filter(function (methodName) {
      return typeof(metaobject[methodName]) == 'function';
    });
  }
  methods.forEach(function (methodName) {
    receiver[methodName] = function () {
      return metaobject[methodName].apply(receiver, arguments);
    };
  });

  return receiver;
};

delegate-to-own

function delegateToOwn (receiver, propertyName, methods) {
  var temporaryMetaobject;

  if (methods == null) {
    temporaryMetaobject = receiver[propertyName];
    methods = Object.keys(temporaryMetaobject).filter(function (methodName) {
      return typeof(temporaryMetaobject[methodName]) == 'function';
    });
  }
  methods.forEach(function (methodName) {
    receiver[methodName] = function () {
      var metaobject = receiver[propertyName];
      return metaobject[methodName].apply(receiver, arguments);
    };
  });

  return receiver;
};

mixin-with-policy

var policies = {
  overwrite: function overwrite (fn1, fn2) {
    return fn1;
  },
  discard: function discard (fn1, fn2) {
    return fn2;
  },
  before: function before (fn1, fn2) {
    return function () {
      var fn1value = fn1.apply(this, arguments),
          fn2value = fn2.apply(this, arguments);
      return fn2value !== void 0
             ? fn2value
             : fn1value;
    }
  },
  after: function after (fn1, fn2) {
    return function () {
      var fn2value = fn2.apply(this, arguments),
          fn1value = fn1.apply(this, arguments);
      return fn2value !== void 0
             ? fn2value
             : fn1value;
    }
  },
  around: function around (fn1, fn2) {
    return function () {
      var argArray = [fn2.bind(this)].concat(__slice.call(arguments, 0));
      return fn1.apply(this, argArray);
    }
  }
};

function inverse (hash) {
  return Object.keys(hash).reduce(function (inversion, policyName) {
    var methodNameOrNames = hash[policyName],
        methodName;
    if (typeof(methodNameOrNames) === 'string') {
      methodName = methodNameOrNames;
      inversion[methodName] = policies[policyName];
    }
    else if (typeof(methodNameOrNames.forEach) === 'function') {
      methodNameOrNames.forEach(function (methodName) {
        inversion[methodName] = policies[policyName];
      });
    }
    return inversion;
  }, {});
}

function mixinWithPolicy (provider, policyDefinition) {
  var policiesByMethodName = inverse(policyDefinition || {}),
      defaultPolicy = policies.overwrite;
  if (policiesByMethodName['*'] != null) {
    defaultPolicy = policiesByMethodName['*'];
    delete policiesByMethodName['*'];
  }
  return function (receiver) {
    receiver = receiver || {};
    Object.keys(provider).forEach(function (key) {
      if (receiver[key] == null) {
        receiver[key] = provider[key];
      }
      else if (policiesByMethodName[key] != null) {
        receiver[key] = policiesByMethodName[key](receiver[key], provider[key]);
      }
      else {
        receiver[key] = defaultPolicy(receiver[key], provider[key]);
      }
    });
    return receiver;
  };
};

safe-extend-privately

var number = 0;

function safeExtendPrivately (prototype, mixin) {
  var safekeepingName = "__" + ++number + "__",
      methodName;

  for (methodName in mixin) {
    if (mixin.hasOwnProperty(methodName)) {
      (function (methodName) {
        prototype[methodName] = function () {
          var context = this[safekeepingName],
              result;
          if (context == null) {
            context = {};
            Object.defineProperty(this, safekeepingName, {
              enumerable: false,
              writable: false,
              value: context
            });
          }
          result = mixin[methodName].apply(context, arguments);
          return (result === context) ? this : result;
        };
      })(methodName);
    };
  };
  return prototype;
}

Greenspun’s Tenth Rule

fluent and selfie

function fluent (methodBody) {
  return function () {
    var value = methodBody.apply(this, arguments);
    
    return value === undefined
           ? this
           : value;
  }
}

function selfie (methodBody) {
  return function () {
    var value = methodBody.apply(this, arguments);
    
    return value === undefined
           ? (this.self || this)
           : value;
  }
}

Encapsulation and Composition

partialProxy

function partialProxy (baseObject, methods, proxyPrototype) {
  var proxyObject = Object.create(proxyPrototype || null);

  methods.forEach(function (methodName) {
    proxyObject[methodName] = function () {
      var result = baseObject[methodName].apply(baseObject, arguments);
      return (result === baseObject)
             ? proxyObject
             : result;
    }
  });

  return proxyObject;
}

policies

var policies = {
  overwrite: function overwrite (fn1, fn2) {
    return fn1;
  },
  discard: function discard (fn1, fn2) {
    return fn2;
  },
  before: function before (fn1, fn2) {
    return function () {
      var fn1value = fn1.apply(this, arguments),
          fn2value = fn2.apply(this, arguments);
      return fn2value !== void 0
             ? fn2value
             : fn1value;
    }
  },
  after: function after (fn1, fn2) {
    return function () {
      var fn2value = fn2.apply(this, arguments),
          fn1value = fn1.apply(this, arguments);
      return fn2value !== void 0
             ? fn2value
             : fn1value;
    }
  },
  around: function around (fn1, fn2) {
    return function () {
      var argArray = [fn2.bind(this)].concat(__slice.call(arguments, 0));
      return fn1.apply(this, argArray);
    }
  }
};

encapsulate

function isUndefined (value) {
  return typeof value === 'undefined';
}

function isntUndefined (value) {
  return typeof value !== 'undefined';
}

function isFunction (value) {
  return typeof value === 'function';
}

var __slice = [].slice;

function extend () {
  var consumer = arguments[0],
      providers = __slice.call(arguments, 1),
      key,
      i,
      provider;

  for (i = 0; i < providers.length; ++i) {
    provider = providers[i];
    for (key in provider) {
      if (Object.prototype.hasOwnProperty.call(provider, key)) {
        consumer[key] = provider[key];
      };
    };
  };
  return consumer;
};

function methodsOfType (behaviour, list, type) {
  return list.filter(function (methodName) {
    return typeof(behaviour[methodName]) === type;
  });
}

function propertyFlags (behaviour) {
  var properties = [],
      propertyName;

  for (propertyName in behaviour) {
    if (behaviour[propertyName] === null) {
      properties.push(propertyName);
    }
  }

  return properties;
}

var number = 0;

function encapsulate (behaviour) {
  var safekeepingName = "__" + ++number + "__",
      properties = Object.keys(behaviour),
      methods = properties.filter(function (methodName) {
          return typeof behaviour[methodName] === 'function';
        }),
      privateMethods = methods.filter(function (methodName) {
          return methodName[0] === '_';
        }),
      publicMethods = methods.filter(function (methodName) {
          return methodName[0] !== '_';
        }),
      dependencies = properties.filter(function (methodName) {
        return isUndefined(behaviour[methodName]);
      }),
      methodsToProxy = publicMethods.concat(dependencies),
      encapsulatedObject = {};

  function createContext (methodReceiver) {
    var innerProxy = partialProxy(methodReceiver, methodsToProxy);

    privateMethods.forEach(function (methodName) {
      innerProxy[methodName] = behaviour[methodName];
    });

    return Object.defineProperty(
      innerProxy,
      'self',
      { writable: false, enumerable: false, value: methodReceiver }
    );
  }

  function getContext (methodReceiver) {
    var context = methodReceiver[safekeepingName];
    if (context == null) {
      context = createContext(methodReceiver);
      Object.defineProperty(methodReceiver, safekeepingName, {
        enumerable: false,
        writable: false,
        value: context
      });
    }
    return context;
  }

  publicMethods.forEach(function (methodName) {
    var methodBody = behaviour[methodName];

    Object.defineProperty(encapsulatedObject, methodName, {
      enumerable: true,
      writable: false,
      value: function () {
        var context = getContext(this),
            result  = methodBody.apply(context, arguments);
        return (result === context) ? this : result;
      }
    });
  });

  dependencies.forEach(function (methodName) {
    if (encapsulatedObject[methodName] == null) {
      encapsulatedObject[methodName] = void 0;
    }
  });

  return Object.preventExtensions(encapsulatedObject);
}

compose-metaobjects

function orderStrategy2 () {
  if (arguments.length === 1) {
    return arguments[0];
  }
  else {
    var fns = __slice.call(arguments, 0);

    return function composed () {
      var args    = arguments,
          context = this,
          values  = fns.map(function (fn) {
            return fn.apply(context, args);
          }).filter(isntUndefined);

      if (values.length > 0) {
        return values[values.length - 1];
      }
    }
  }
}

function propertiesToArrays (metaobjects) {
  return metaobjects.reduce(function (collected, metaobject) {
    var key;

    for (key in metaobject) {
      if (key in collected) {
        collected[key].push(metaobject[key]);
      }
      else collected[key] = [metaobject[key]]
    }
    return collected;
  }, Object.create(null))
}

function resolveUndefineds (collected) {
  return Object.keys(collected).reduce(function (resolved, key) {
    var values = collected[key];

    if (values.every(isUndefined)) {
      resolved[key] = undefined;
    }
    else resolved[key] = values.filter(isntUndefined);

    return resolved;
  }, {});
}

function applyProtocol(seed, resolveds, protocol) {
  return Object.keys(resolveds).reduce( function (applied, key) {
    var value = resolveds[key];

    if (isUndefined(value)) {
      applied[key] = value;
    }
    else if (value.every(isFunction)) {
      applied[key] = protocol.apply(null, value);
    }
    else throw "Don't know what to do with " + value;

    return applied;
  }, seed);
}

function canBeMergedInto (object1, object2) {
  var prototype1 = Object.getPrototypeOf(object1),
      prototype2 = Object.getPrototypeOf(object2);

  if (prototype1 === null) return prototype2 === null;
  if (prototype2 === null) return true;
  if (prototype1 === prototype2) return true;

  return Object.prototype.isPrototypeOf.call(prototype2, prototype1);
}

// shim if allong.es.callLeft not available

var callLeft2 = (function () {
  if (typeof callLeft == 'function') {
    return callLeft;
  }
  else if (typeof allong === 'object' && typeof allong.es === 'object' && typeof\
 allong.es.callLeft === 'function') {
    return allong.es.callLeft;
  }
  else {
    return function callLeft2 (fn, arg2) {
      return function callLeft2ed (arg1) {
        return fn.call(this, arg1, arg2);
      };
    };
  }
})();

function seedFor (objectList) {
  var seed = objectList[0] == null
             ? Object.create(null)
             : Object.create(Object.getPrototypeOf(objectList[0])),
      isCompatibleWithSeed = callLeft2(canBeMergedInto, seed);

  if (!objectList.every(isCompatibleWithSeed)) throw 'incompatible prototypes';
  return seed;
}

function composeMetaobjects () {
  var metaobjects = __slice.call(arguments, 0),
      arrays      = propertiesToArrays(metaobjects),
      resolved    = resolveUndefineds(arrays),
      seed        = seedFor(metaobjects),
      composed    = applyProtocol(seed, resolved, orderStrategy2);

  return composed;
}

newable

function Newable (optionalName, metaobject, optionalSuper) {
	var name        = typeof(optionalName) === 'string'
	                  ? optionalName
										: '',
		  metaobject  = typeof(optionalName) === 'string'
			              ? metaobject
										: optionalName,
		  superClazz  = typeof(optionalName) === 'string'
			              ? optionalSuper
										: metaobject,
			source      = "(function " + name + " () { " +
                        "var r = constructor.apply(this, arguments); " +
                        "return r === undefined ? this : r; " +
                      "})",
			clazz       = eval(source),
      constructor;

  if (typeof(metaobject.constructor) === 'function' && typeof(optionalSuper) ===\
 'function') {
    constructor = function () {
      optionalSuper.apply(this, arguments);
      return metaobject.constructor.apply(this, arguments);
    }
  }
  else if (typeof(metaobject.constructor) === 'function') {
    constructor = metaobject.constructor;
  }
  else if (typeof(optionalSuper) === 'function') {
    constructor = optionalSuper;
  }
  else constructor = function () {}

	clazz.prototype = extend({}, metaobject);
	delete clazz.prototype.constructor;
	return clazz;
}

Inheritance

struct

function Struct () {
  var name = arguments[0],
      keys = [].slice.call(arguments, 1),
      constructor = eval("(function "+name+"(argument) { return initialize.call(\
this, argument); })");

  function initialize (argument) {
    if (constructor.prototype.isPrototypeOf(this)) {
      var argument = argument,
          struct = this;

      keys.forEach(function (key) {
        Object.defineProperty(struct, key, {
          enumerable: true,
          writable: true,
          value: argument[key]
        });
      });
      return Object.preventExtensions(struct);
    }
    else return constructor.prototype.isPrototypeOf(argument);
  };

  constructor.assertIsPrototypeOf = function (argument) {
    if (!constructor.prototype.isPrototypeOf(argument)) {
      var name = constructor.name === ''
                 ? "Struct(" + keys.join(", ") + ")"
                 : constructor.name;
      throw "Type Error: " + argument + " is not a " + name;
    }
    else return argument;
  }

  return constructor;
}

Utility Functions

Throughout this book, we have used utility functions in our code snippets and examples. Here is a list of functions we’ve borrowed from other sources. Most are from underscore or allong.es.

var allong = require('allong.es');
var _ = require('underscore');

extend

extend can be found in the underscore library:

var extend = _.extend;

Or you can use this formulation:

var __slice = [].slice;

function extend () {
  var consumer = arguments[0],
      providers = __slice.call(arguments, 1),
      key,
      i,
      provider;

  for (i = 0; i < providers.length; ++i) {
    provider = providers[i];
    for (key in provider) {
      if (provider.hasOwnProperty(key)) {
        consumer[key] = provider[key];
      };
    };
  };
  return consumer;
};

pipeline

pipeline composes functions in the order they are applied:

var pipeline = allong.es.pipeline;

function square (n) { return n * n; }
function increment (n) { return n + 1; }

pipeline(square, increment)(6)
  //=> 37

tap

tap passes a value to a function, discards the result, and returns the original value:

var tap = allong.es.tap;

tap(6, function (n) {
  console.log("Hello there, " + n);
});
  //=>
    Hello there, 6
    6

map

map applies function to an array, returning an array of the results:

var map = allong.es.map;

map([1, 2, 3], function (n) {
  return n * n;
});
  //=> [1, 4, 9]

variadic

variadic takes a function and returns a “variadic” function, one that collects arguments in an array and passes them to the last parameter:

var variadic = allong.es.variadic;

var a = variadic(function (args) {
  return { args: args };
});

a(1, 2, 3, 4, 5)
  //=> { args: [1, 2, 3, 4, 5] }

var b = variadic(function (first, second, rest) {
  return { first: first, second: second, rest: rest };
});

b(1, 2, 3, 4, 5)
  //=> { first: 1, second: 2, rest: [3, 4, 5] }

unvariadic

unvariadic takes a variadic function and turns it into a function with a fixed arity:

var unvariadic = allong.es.unvariadic;

function ensuresArgumentsAreNumbers (fn) {
  return unvariadic(fn.length, function () {
    for (var i in arguments) {
      if (typeof(arguments[i]) !== 'number') {
        throw "Ow! NaN!!"
      }
    }
    return fn.apply(this, arguments);
  });
}

function myAdd (a, b) {
  return a + b;
}

var myCheckedAdd = ensuresArgumentsAreNumbers(myAdd);

myCheckedAdd.length
  //=> 2