Skip to content

Instantly share code, notes, and snippets.

@casecode
Last active August 29, 2015 14:21
Show Gist options
  • Save casecode/57b938443e426893f764 to your computer and use it in GitHub Desktop.
Save casecode/57b938443e426893f764 to your computer and use it in GitHub Desktop.
// JSBin: http://jsbin.com/yugeyeliti/edit?js,console
function assertEqual (test, a, b) {
if (!b) {
b = a;
a = test;
test = null;
}
test = test ? '(' + test + ')' : '';
function pass () {
console.log('PASS ' + test + ': (' + a + ') is deeply equal to (' + b + ')');
}
function fail () {
console.log('FAIL ' + test +': expected (' + a + ') to be deeply equal to (' + b + ')');
}
a === b ? pass() : fail(); // jshint ignore:line
}
// Currying
function curry (fn, len) {
return function wait () {
var fnlen = len || fn.length,
slice = [].slice,
args = slice.call(arguments);
if (args.length >= fnlen) return fn.apply(null, args);
return function () {
return wait.apply(null, args.concat(slice.call(arguments)));
};
};
}
var get = curry(function (prop, obj) {
return obj[prop];
});
var clearheaders = [
{ name: 'The Toms' },
{ name: 'Top Hog' },
{ name: 'Som Chai' }
];
var names = clearheaders.map(get('name'));
assertEqual('Clearheaders test', names.join(', '), 'The Toms, Top Hog, Som Chai');
var sum = curry(function () {
var args = [].slice.call(arguments);
return args.reduce(function(prev, curr) {
return prev + curr;
});
}, 2); // Assume at least two numbers needed to sum (for testing purposes)
assertEqual('Expect FAIL - can\'t sum just one number (for this example at least)', sum(1), 1);
assertEqual(sum(1, 2), 3);
assertEqual(sum(1)(2, 3), 6);
// Compose and its inverse Pipe
function _chain (processors) {
return function() {
var args = arguments;
var output;
processors.forEach(function (processor, idx) {
output = (
typeof processor === 'function' ?
processor.apply(null, (idx === 0 ? args : [output])) :
output
);
});
return output;
};
}
function compose () {
var processors = [].slice.call(arguments).reverse();
return _chain(processors);
}
function pipe () {
var processors = [].slice.call(arguments);
return _chain(processors);
}
var square = function (x) { return x * x; };
var subtractThree = function (x) { return x - 3; };
var pipedSumThenSquareThenSubtractThree = pipe(sum, square, subtractThree);
var pipedResult = pipedSumThenSquareThenSubtractThree(2, 3);
assertEqual('Pipe Math', pipedResult, 22);
// Arguments reversed when composing
var composedSumThenSquareThenSubtractThree = compose(subtractThree, square, sum);
var composedResult = composedSumThenSquareThenSubtractThree(2, 3);
assertEqual('Compose Math', composedResult, 22);
// When piping, if functions rely on a certain context, use bind
var metric = {
_val: 4,
_get: function () { return this._val; },
_set: function (x) { this._val = x; },
update: function (x) {
this._set(this._get() + x);
return this._get();
}
};
function adjustedDelta (raw) {
return raw / 2;
}
function tryUpdatingMetic (fn, rawDelta) {
try {
return fn(rawDelta);
} catch (err) {
return err.message;
}
}
// The following will throw an error as context of metric.update changed during piping
var failToUpdateMetricWithDelta = pipe(adjustedDelta, metric.update);
assertEqual('Expect FAIL - functions not bound', tryUpdatingMetic(failToUpdateMetricWithDelta, 4), 6);
// Solution is to specify binding
var updateMetric = metric.update.bind(metric);
var updateMetricWithDelta = pipe(adjustedDelta, updateMetric);
assertEqual(tryUpdatingMetic(updateMetricWithDelta, 4), 6);
// Functors
var map = curry(function (fn, obj) {
return obj.map(fn);
});
function _Identity (val) {
this.val = val;
}
function Identity (val) {
return new _Identity(val);
}
_Identity.prototype.map = function (fn) {
return Identity(fn(this.val));
};
function _Maybe (val) {
this.val = val || null;
}
function Maybe (val) {
return new _Maybe(val);
}
_Maybe.prototype.map = function (fn) {
return this.val ? Maybe(fn(this.val)) : Maybe(null);
};
var unwrap = get('val');
function toUpperCase (str) {
return str.toUpperCase();
}
var greeting = Identity('hello');
var screamIt = pipe(map(toUpperCase), unwrap);
assertEqual(screamIt(greeting), 'HELLO');
// What if we are accessing a property that could be undefined?
// We can use Maybe for automatic null checks
var me = { name: 'Case' };
var nameInCaps = pipe(get('name'), Maybe, map(toUpperCase), unwrap);
assertEqual(nameInCaps(me), 'CASE');
// Built-in null check prevents error from being thrown
var name2InCaps = pipe(get('name2'), Maybe, map(toUpperCase), get('val'));
assertEqual(name2InCaps(me), null); // No error, just returns null
var clearheadersInCaps = clearheaders.map(nameInCaps).join(', ');
assertEqual(clearheadersInCaps, 'THE TOMS, TOP HOG, SOM CHAI');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment