Last active
August 29, 2015 14:21
-
-
Save casecode/57b938443e426893f764 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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