- Composition + Single responsibility
- Reuse
- Independent evolution of elements
- Less friction to test and document
- Clearer points of extension and modification
- Systematic approach
- Convention for human
- Mechanical automation
- Separate definition and use Isolated definitions allow building concrete hierarchies though relational combination like normalization in relational databases
- Separate data and functions
- Functions or procedures generally encode information that is coupled to the run-time platform where it will be interpreted
- Data descriptions are much less tightly coupled and it is much cheaper to
map to a different encoding. For example it is relatively easy to have one
data definition from which to mechanically derive:
- The database schema
- The html form for the UI
- The JSON format for client/server exchange
- Validation rules for different run-times (e.g.: JavaScript and Java)
- Primitives
- Combination: ADTs and Objects
- Abstracting dispatch: Generic functions
- Data
- Built-in types:
Undefined
,Null
,Boolean
,String
,Number
, andObject
- Functions
- On built-in types Maximum re-usability
function distance(x0, y0, x1, y1) {
var x = x0 - x1;
var y = y0 - y1;
return Math.sqrt(x * x + y * y);
}
versus
function distance(point1, point2) {
var x = point1.x - point2.x;
var y = point1.y - point2.y;
return Math.sqrt(x * x + y * y);
}
// or
Point.prototype.distance = function(point) {
var x = this.x - point.x;
var y = this.y - point.y;
return Math.sqrt(x * x + y * y);
};
While both define the relationship between elements, the later is coupled with the responsibility of destructuring and is tied to a specific representation.
- ADTs
- mapping aggregate data to functions on primitives
'use strict';
function distance(x0, y0, x1, y1) {
var x = x0 - x1;
var y = y0 - y1;
return Math.sqrt(x * x + y * y);
}
function move(origin, delta) {
return origin + delta;
}
var Point = function Point(x, y) {
return { x: x, y: y };
};
Point.move = function movePoint(point1, point2) {
var x = move(point1.x, point2.x);
var y = move(point1.y, point2.y);
return Point(x, y);
};
Point.distance = function distanceToPoint(point1, point2) {
return distance(point1.x, point1.y, point2.x, point2.y);
};
- Objects
- partially applied ADTs
'use strict';
function createPointInstance(x, y) {
var pointInstance = new Point(x, y);
return {
move: function move(point2) {
return Point.move(pointInstance, point2);
},
distance: function distance(point2) {
return Point.distance(pointInstance, point2);
}
};
}
// or
function createPointInstance(x, y) {
var pointInstance = new Point(x, y);
return Object.keys(Point).reduce(function r(acc, key) {
acc[key] = Point[key].bind(null, pointInstance);
return acc;
}, {});
}
// or
function createInstance0(Obj /* consArgs */) {
var consArgs = Array.prototype.slice.call(arguments, 1);
var instance = Obj.apply(Obj, consArgs);
return Object.keys(Obj).reduce(function r(acc, key) {
acc[key] = Obj[key].bind(null, instance);
return acc;
}, {});
}
// or
function partial(instance, obj) {
return Object.keys(obj).reduce(function r(acc, key) {
acc[key] = obj[key].bind(null, instance);
return acc;
}, {});
}
function createPointInstance(x, y) {
return partial(new Point(x, y), Point);
}
'use strict';
var functions = {
a: function a(obj, arg) { return 'a fn; ' + obj['@type'] + '; ' + arg + ';'; },
b: function b(obj, arg) { return 'b fn; ' + obj['@type'] + '; ' + arg + ';'; }
};
function typeSelector(meths, args) { return meths[args[0]['@type']]; }
var selector = typeSelector.bind(null, functions);
function ap(sel /* args */) {
var args = Array.prototype.slice.call(arguments, 1);
var fn = sel(args);
return fn.apply(null, args);
}
var ab = ap.bind(null, selector);
var a = { '@type': 'a' };
var b = { '@type': 'b' };
console.log(ab(a, 'a args'), ab(b, 'b args'));