Appendix: Source Code - JavaScript Spessore: A thick shot of objects, metaobjects, & protocols (2014)

JavaScript Spessore: A thick shot of objects, metaobjects, & protocols (2014)

Appendix: Source Code

34

Encapsulation and Composition

var __slice = [].slice;

function isUndefined (value) {

return typeof value === 'undefined';

}

function isntUndefined (value) {

return typeof value !== 'undefined';

}

function isFunction (value) {

return typeof value === 'function';

}

////////////////////////////////////////////////////////////

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;

};

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);

}

}

};

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 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;

}

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);

}

/////////////////////////////////////////////////////////

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;

}

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;

}

Methods

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.apply\

(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);

}

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);

}

function MatchTypes () {

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);

}

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

1. Group (c) 2013 J MacPherson, some rights reserved

2. A “Lisp-1” has a single namespace for both functions and other values. A “Lisp-2” has separate namespaces for functions and other values. To the extend that JavaScript resembles a Lisp, it resembles a Lisp-1. See The function namespace.

3. Mazzer Mini Grinder, some rights reserved

4. JavaScript does not actually pass functions or any other kind of object around, it passes references to functions and other objects around. But it’s awkward to describe var I = function (a) { return a; } as binding a reference to a function to the variable I, so we often take an intellectually lazy shortcut, and say the function is bound to the variable I. This is much the same shorthand as saying that somebody added me to the schedule for an upcoming conference: They really added my name to the list, which is a kind of reference to me.

5. This exercise was snarfed from The Four Rules of Simple Design

6. Transparent Sanremo espresso machine, London Coffee Festival, Truman Brewery, Brick Lane, Hackney, London, UK (c) 2013 Cory Doctorow, some rights reserved

7. These function names go back to Lisp 1.5. The story is that it was written for a hardware architecture that had two fast CPU registers, an address register and a decrement register. The core data type was called a “cons cell,” and represented two pointers. One was stored in the address register, the other in the decrement register. The function car meant “contents of address register,” and the function cdr meant “contents of the decrement register.” cons meant “create a new cons cell.” The would have called map “mapcar,” but we’re sticking with the more contemporary term.

8. “A language construct that facilitates the bundling of data with the methods (or other functions) operating on that data.”–Wikipedia

9. Yes, there’s another way to track the size of the array, but we don’t need it to demonstrate encapsulation and hiding of state.

10.And when you take an already factored component and rearrange things so that it is factored into a different set of subcomponents without altering its behaviour, you are refactoring.

11.Before you start wondering whether a deque is-a queue, we said nothing about types and classes. This relationship is called was-a, or “implemented in terms of a.”

12.JavaScript also does other things with this as well, but this is all we care about right now.

13.Too bad the language binds the context to the name this instead of the name context!

14.Just enough to be frustrating, to be perfectly candid!

15.Londinium 1 Lever Espresso Machine (c) 2013 Alejandro Erickson, some rights reserved

16.JavaScript also provides a single method that can close an object for modification, extension, and configuration at the same time: Object.freeze(...).

17.Vacuum Pots (c) 2012 Olin Viydo, some rights reserved

18.For many programmers, the distinction between a dynamic relationship and a copying mechanism is too fine to worry about. However, it makes many dynamic program modifications possible.

19.Recall that Strings, Numbers, Booleans and so forth are value types and primitives. We’re calling them primitives here.

20.Mmm … Vivace’s Espresso! (c) 2004 Allan Haggert, some rights reserved

21.Later on, we’ll see how to use method combinators to do this more elegantly.

22.With the greatest respect to Chongo, author of “The Homeless Interpretation of Quantum Mechanics.”

23.Robert Martin’s rule of thumb for determining whether a method has a single responsibility is to ask when and why it would ever change. If there is just one reason why you are likely to change a method, it has a single responsibility. If there is more than one reason why it might change, it should be decomposed into separate entities that each have a single responsibility.

24.Krups Machines (c) 2010 Shadow Becomes White, some rights reserved

25.Transpile-to-JavaScript languages like CoffeeScript, and the upcoming EcmaScript 6 verison of JavaScript provide destructuring to help with the syntax of things like named parameters. But the mechanism is identical.

26.Nespresso Capsules (c) 2011 André Nieto Porras, some rights reserved

27.This example of the Extract Method refactoring comes from Martin Fowler’s excellent Catalogue of Refactorings.

28.We will deliberately leave out some refinements such as self and private methods to highlight the ideas we’re exploring here.

29.Royal Java Distributed by the Boston Public Library, no known rights.

30.There’re good things we can say about why we should consider making an amount property, and/or encapsulate these structs so they behave like objects, but this gives the general idea of structural typing.

31.image (c) 2010 Christina Spicuzza. Some rights reserved

32.image (c) 2011. Some rights reserved

33.Cime Espresso Machine Factory, Copyright 2012, Jong Hoon Lee Some Rights Reserved

34.Coffee Chart (c) 2012 Michael Coghlan, some rights reserved