Created
January 4, 2016 09:32
-
-
Save luislee818/9d259f50eda997bb0344 to your computer and use it in GitHub Desktop.
Sources of the Functional JavaScript book, combined
This file contains hidden or 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
// Chap 1 | |
function splat(fun) { | |
return function(array) { | |
return fun.apply(null, array); | |
}; | |
} | |
var addArrayElements = splat(function(x, y) { return x + y }); | |
addArrayElements([1, 2]); | |
//=> 3 | |
function unsplat(fun) { | |
return function() { | |
return fun.call(null, _.toArray(arguments)); | |
}; | |
} | |
var joinElements = unsplat(function(array) { return array.join(' ') }); | |
joinElements(1, 2); | |
//=> "1 2" | |
joinElements('-', '$', '/', '!', ':'); | |
//=> "- $ / ! :" | |
function parseAge(age) { | |
if (!_.isString(age)) throw new Error("Expecting a string"); | |
var a; | |
console.log("Attempting to parse an age"); | |
a = parseInt(age, 10); | |
if (_.isNaN(a)) { | |
console.log(["Could not parse age:", age].join(' ')); | |
a = 0; | |
} | |
return a; | |
} | |
function fail(thing) { | |
throw new Error(thing); | |
} | |
function warn(thing) { | |
console.log(["WARNING:", thing].join(' ')); | |
} | |
function note(thing) { | |
console.log(["NOTE:", thing].join(' ')); | |
} | |
function parseAge(age) { | |
if (!_.isString(age)) fail("Expecting a string"); | |
var a; | |
note("Attempting to parse an age"); | |
a = parseInt(age, 10); | |
if (_.isNaN(a)) { | |
warn(["Could not parse age:", age].join(' ')); | |
a = 0; | |
} | |
return a; | |
} | |
var letters = ['a', 'b', 'c']; | |
letters[1]; | |
//=> 'b' | |
function naiveNth(a, index) { | |
return a[index]; | |
} | |
function isIndexed(data) { | |
return _.isArray(data) || _.isString(data); | |
} | |
function nth(a, index) { | |
if (!_.isNumber(index)) fail("Expected a number as the index"); | |
if (!isIndexed(a)) fail("Not supported on non-indexed type"); | |
if ((index < 0) || (index > a.length - 1)) | |
fail("Index value is out of bounds"); | |
return a[index]; | |
} | |
function second(a) { | |
return nth(a, 1); | |
} | |
function compareLessThanOrEqual(x, y) { | |
if (x < y) return -1; | |
if (y < x) return 1; | |
return 0; | |
} | |
[2, 3, -1, -6, 0, -108, 42, 10].sort(compareLessThanOrEqual); | |
//=> [-108, -6, -1, 0, 2, 3, 10, 42] | |
function lessOrEqual(x, y) { | |
return x <= y; | |
} | |
function comparator(pred) { | |
return function(x, y) { | |
if (truthy(pred(x, y))) | |
return -1; | |
else if (truthy(pred(y, x))) | |
return 1; | |
else | |
return 0; | |
}; | |
}; | |
function lameCSV(str) { | |
return _.reduce(str.split("\n"), function(table, row) { | |
table.push(_.map(row.split(","), function(c) { return c.trim()})); | |
return table; | |
}, []); | |
}; | |
var peopleTable = lameCSV("name,age,hair\nMerble,35,red\nBob,64,blonde"); | |
peopleTable; | |
//=> [["name", "age", "hair"], | |
// ["Merble", "35", "red"], | |
// ["Bob", "64", "blonde"]] | |
function selectNames(table) { | |
return _.rest(_.map(table, _.first)); | |
} | |
function selectAges(table) { | |
return _.rest(_.map(table, second)); | |
} | |
function selectHairColor(table) { | |
return _.rest(_.map(table, function(row) { | |
return nth(row, 2); | |
})); | |
} | |
var mergeResults = _.zip; | |
function existy(x) { return x != null }; | |
function truthy(x) { return (x !== false) && existy(x) }; | |
function doWhen(cond, action) { | |
if(truthy(cond)) | |
return action(); | |
else | |
return undefined; | |
} | |
function executeIfHasField(target, name) { | |
return doWhen(existy(target[name]), function() { | |
var result = _.result(target, name); | |
console.log(['The result is', result].join(' ')); | |
return result; | |
}); | |
} | |
// Chap 2 | |
var lyrics = []; | |
for (var bottles = 99; bottles > 0; bottles--) { | |
lyrics.push(bottles + " bottles of beer on the wall"); | |
lyrics.push(bottles + " bottles of beer"); | |
lyrics.push("Take one down, pass it around"); | |
if (bottles > 1) { | |
lyrics.push((bottles - 1) + " bottles of beer on the wall."); | |
} | |
else { | |
lyrics.push("No more bottles of beer on the wall!"); | |
} | |
} | |
function lyricSegment(n) { | |
return _.chain([]) | |
.push(n + " bottles of beer on the wall") | |
.push(n + " bottles of beer") | |
.push("Take one down, pass it around") | |
.tap(function(lyrics) { | |
if (n > 1) | |
lyrics.push((n - 1) + " bottles of beer on the wall."); | |
else | |
lyrics.push("No more bottles of beer on the wall!"); | |
}) | |
.value(); | |
} | |
function song(start, end, lyricGen) { | |
return _.reduce(_.range(start,end,-1), | |
function(acc,n) { | |
return acc.concat(lyricGen(n)); | |
}, []); | |
} | |
var nums = [1,2,3,4,5]; | |
function doubleAll(array) { | |
return _.map(array, function(n) { return n*2 }); | |
} | |
doubleAll(nums); | |
//=> [2, 4, 6, 8, 10] | |
function average(array) { | |
var sum = _.reduce(array, function(a, b) { return a+b }); | |
return sum / _.size(array); | |
} | |
average(nums); | |
//=> 3 | |
/* grab only even numbers in nums */ | |
function onlyEven(array) { | |
return _.filter(array, function(n) { | |
return (n%2) === 0; | |
}); | |
} | |
onlyEven(nums); | |
//=> [2, 4] | |
function allOf(/* funs */) { | |
return _.reduceRight(arguments, function(truth, f) { | |
return truth && f(); | |
}, true); | |
} | |
function anyOf(/* funs */) { | |
return _.reduceRight(arguments, function(truth, f) { | |
return truth || f(); | |
}, false); | |
} | |
function complement(pred) { | |
return function() { | |
return !pred.apply(null, _.toArray(arguments)); | |
}; | |
} | |
function cat() { | |
var head = _.first(arguments); | |
if (existy(head)) | |
return head.concat.apply(head, _.rest(arguments)); | |
else | |
return []; | |
} | |
cat([1,2,3], [4,5], [6,7,8]); | |
//=> [1, 2, 3, 4, 5, 6, 7, 8] | |
function construct(head, tail) { | |
return cat([head], _.toArray(tail)); | |
} | |
construct(42, [1,2,3]); | |
//=> [42, 1, 2, 3] | |
function mapcat(fun, coll) { | |
return cat.apply(null, _.map(coll, fun)); | |
} | |
function butLast(coll) { | |
return _.toArray(coll).slice(0, -1); | |
} | |
function interpose (inter, coll) { | |
return butLast(mapcat(function(e) { | |
return construct(e, [inter]); | |
}, | |
coll)); | |
} | |
var zombie = {name: "Bub", film: "Day of the Dead"}; | |
_.keys(zombie); | |
//=> ["name", "film"] | |
_.values(zombie); | |
//=> ["Bub", "Day of the Dead"] | |
var library = [{title: "SICP", isbn: "0262010771", ed: 1}, | |
{title: "SICP", isbn: "0262510871", ed: 2}, | |
{title: "Joy of Clojure", isbn: "1935182641", ed: 1}]; | |
_.findWhere(library, {title: "SICP", ed: 2}); | |
//=> {title: "SICP", isbn: "0262510871", ed: 2} | |
function project(table, keys) { | |
return _.map(table, function(obj) { | |
return _.pick.apply(null, construct(obj, keys)); | |
}); | |
}; | |
function rename(obj, newNames) { | |
return _.reduce(newNames, function(o, nu, old) { | |
if (_.has(obj, old)) { | |
o[nu] = obj[old]; | |
return o; | |
} | |
else | |
return o; | |
}, | |
_.omit.apply(null, construct(obj, _.keys(newNames)))); | |
}; | |
function as(table, newNames) { | |
return _.map(table, function(obj) { | |
return rename(obj, newNames); | |
}); | |
}; | |
function restrict(table, pred) { | |
return _.reduce(table, function(newTable, obj) { | |
if (truthy(pred(obj))) | |
return newTable; | |
else | |
return _.without(newTable, obj); | |
}, table); | |
}; | |
// Chap 3 | |
function makeEmptyObject() { | |
return new Object(); | |
} | |
var globals = {}; | |
function makeBindFun(resolver) { | |
return function(k, v) { | |
var stack = globals[k] || []; | |
globals[k] = resolver(stack, v); | |
return globals; | |
}; | |
} | |
var stackBinder = makeBindFun(function(stack, v) { | |
stack.push(v); | |
return stack; | |
}); | |
var stackUnbinder = makeBindFun(function(stack) { | |
stack.pop(); | |
return stack; | |
}); | |
var dynamicLookup = function(k) { | |
var slot = globals[k] || []; | |
return _.last(slot); | |
}; | |
function f() { return dynamicLookup('a'); }; | |
function g() { stackBinder('a', 'g'); return f(); }; | |
f(); | |
//=> 1 | |
g(); | |
//=> 'g' | |
globals; | |
// {a: [1, "g"], b: [100]} | |
function strangeIdentity(n) { | |
// intentionally strange | |
for(var i=0; i<n; i++); | |
return i; | |
} | |
strangeIdentity(138); | |
//=> 138 | |
function strangeIdentity(n) { | |
var i; | |
for(i=0; i<n; i++); | |
return i; | |
} | |
function strangerIdentity(n) { | |
// intentionally stranger still | |
for(this['i'] = 0; this['i']<n; this['i']++); | |
return this['i']; | |
} | |
strangerIdentity(108); | |
//=> 108 | |
function createScaleFunction(FACTOR) { | |
return function(v) { | |
return _.map(v, function(n) { | |
return (n * FACTOR); | |
}); | |
}; | |
} | |
var scale10 = createScaleFunction(10); | |
scale10([1,2,3]); | |
//=> [10, 20, 30] | |
function createWeirdScaleFunction(FACTOR) { | |
return function(v) { | |
this['FACTOR'] = FACTOR; | |
var captures = this; | |
return _.map(v, _.bind(function(n) { | |
return (n * this['FACTOR']); | |
}, captures)); | |
}; | |
} | |
var scale10 = createWeirdScaleFunction(10); | |
scale10.call({}, [5,6,7]); | |
//=> [50, 60, 70]; | |
function makeAdder(CAPTURED) { | |
return function(free) { | |
return free + CAPTURED; | |
}; | |
} | |
var add10 = makeAdder(10); | |
add10(32); | |
//=> 42 | |
function averageDamp(FUN) { | |
return function(n) { | |
return average([n, FUN(n)]); | |
} | |
} | |
var averageSq = averageDamp(function(n) { return n * n }); | |
averageSq(10); | |
//=> 55 | |
function complement(PRED) { | |
return function() { | |
return !PRED.apply(null, _.toArray(arguments)); | |
}; | |
} | |
function isEven(n) { return (n%2) === 0 } | |
var isOdd = complement(isEven); | |
isOdd(2); | |
//=> false | |
isOdd(413); | |
//=> true | |
function plucker(FIELD) { | |
return function(obj) { | |
return (obj && obj[FIELD]); | |
}; | |
} | |
var best = {title: "Infinite Jest", author: "DFW"}; | |
var getTitle = plucker('title'); | |
getTitle(best); | |
//=> "Infinite Jest" | |
var books = [{title: "Chthon"}, {stars: 5}, {title: "Botchan"}]; | |
var third = plucker(2); | |
third(books); | |
//=> {title: "Botchan"} | |
// Chap 4 | |
var people = [{name: "Fred", age: 65}, {name: "Lucy", age: 36}]; | |
_.max(people, function(p) { return p.age }); | |
//=> {name: "Fred", age: 65} | |
function finder(valueFun, bestFun, coll) { | |
return _.reduce(coll, function(best, current) { | |
var bestValue = valueFun(best); | |
var currentValue = valueFun(current); | |
return (bestValue === bestFun(bestValue, currentValue)) ? best : current; | |
}); | |
} | |
function best(fun, coll) { | |
return _.reduce(coll, function(x, y) { | |
return fun(x, y) ? x : y; | |
}); | |
} | |
best(function(x,y) { return x > y }, [1,2,3,4,5]); | |
//=> 5 | |
function repeat(times, VALUE) { | |
return _.map(_.range(times), function() { return VALUE; }); | |
} | |
repeat(4, "Major"); | |
//=> ["Major", "Major", "Major", "Major"] | |
function repeatedly(times, fun) { | |
return _.map(_.range(times), fun); | |
} | |
repeatedly(3, function() { | |
return Math.floor((Math.random()*10)+1); | |
}); | |
//=> [1, 3, 8] | |
function iterateUntil(fun, check, init) { | |
var ret = []; | |
var result = fun(init); | |
while (check(result)) { | |
ret.push(result); | |
result = fun(result); | |
} | |
return ret; | |
}; | |
function always(VALUE) { | |
return function() { | |
return VALUE; | |
}; | |
}; | |
function invoker (NAME, METHOD) { | |
return function(target /* args ... */) { | |
if (!existy(target)) fail("Must provide a target"); | |
var targetMethod = target[NAME]; | |
var args = _.rest(arguments); | |
return doWhen((existy(targetMethod) && METHOD === targetMethod), function() { | |
return targetMethod.apply(target, args); | |
}); | |
}; | |
}; | |
var rev = invoker('reverse', Array.prototype.reverse); | |
_.map([[1,2,3]], rev); | |
//=> [[3,2,1]] | |
function uniqueString(len) { | |
return Math.random().toString(36).substr(2, len); | |
}; | |
uniqueString(10); | |
//=> "3rm6ww5w0x" | |
function uniqueString(prefix) { | |
return [prefix, new Date().getTime()].join(''); | |
}; | |
uniqueString("argento"); | |
//=> "argento1356107740868" | |
function makeUniqueStringFunction(start) { | |
var COUNTER = start; | |
return function(prefix) { | |
return [prefix, COUNTER++].join(''); | |
} | |
}; | |
var uniqueString = makeUniqueStringFunction(0); | |
uniqueString("dari"); | |
//=> "dari0" | |
uniqueString("dari"); | |
//=> "dari1" | |
var generator = { | |
count: 0, | |
uniqueString: function(prefix) { | |
return [prefix, this.count++].join(''); | |
} | |
}; | |
generator.uniqueString("bohr"); | |
//=> bohr0 | |
generator.uniqueString("bohr"); | |
//=> bohr1 | |
var omgenerator = (function(init) { | |
var COUNTER = init; | |
return { | |
uniqueString: function(prefix) { | |
return [prefix, COUNTER++].join(''); | |
} | |
}; | |
})(0); | |
omgenerator.uniqueString("lichking-"); | |
//=> "lichking-0" | |
var nums = [1,2,3,null,5]; | |
_.reduce(nums, function(total, n) { return total * n }); | |
//=> 0 | |
function fnull(fun /*, defaults */) { | |
var defaults = _.rest(arguments); | |
return function(/* args */) { | |
var args = _.map(arguments, function(e, i) { | |
return existy(e) ? e : defaults[i]; | |
}); | |
return fun.apply(null, args); | |
}; | |
}; | |
var safeMult = fnull(function(total, n) { return total * n }, 1, 1); | |
_.reduce(nums, safeMult); | |
//=> 30 | |
function defaults(d) { | |
return function(o, k) { | |
var val = fnull(_.identity, d[k]); | |
return o && val(o[k]); | |
}; | |
} | |
function doSomething(config) { | |
var lookup = defaults({critical: 108}); | |
return lookup(config, 'critical'); | |
} | |
doSomething({critical: 9}); | |
//=> 9 | |
doSomething({}); | |
//=> 108 | |
function checker(/* validators */) { | |
var validators = _.toArray(arguments); | |
return function(obj) { | |
return _.reduce(validators, function(errs, check) { | |
if (check(obj)) | |
return errs; | |
else | |
return _.chain(errs).push(check.message).value(); | |
}, []); | |
}; | |
} | |
function validator(message, fun) { | |
var f = function(/* args */) { | |
return fun.apply(fun, arguments); | |
}; | |
f['message'] = message; | |
return f; | |
} | |
function aMap(obj) { | |
return _.isObject(obj); | |
} | |
var checkCommand = checker(validator("must be a map", aMap)); | |
function hasKeys() { | |
var KEYS = _.toArray(arguments); | |
var fun = function(obj) { | |
return _.every(KEYS, function(k) { | |
return _.has(obj, k); | |
}); | |
}; | |
fun.message = cat(["Must have values for keys:"], KEYS).join(" "); | |
return fun; | |
} | |
var checkCommand = checker(validator("must be a map", aMap), hasKeys('msg', 'type')); | |
// Chap 5 | |
function dispatch(/* funs */) { | |
var funs = _.toArray(arguments); | |
var size = funs.length; | |
return function(target /*, args */) { | |
var ret = undefined; | |
var args = _.rest(arguments); | |
for (var funIndex = 0; funIndex < size; funIndex++) { | |
var fun = funs[funIndex]; | |
ret = fun.apply(fun, construct(target, args)); | |
if (existy(ret)) return ret; | |
} | |
return ret; | |
}; | |
} | |
var str = dispatch(invoker('toString', Array.prototype.toString), | |
invoker('toString', String.prototype.toString)); | |
str("a"); | |
//=> "a" | |
str(_.range(10)); | |
//=> "0,1,2,3,4,5,6,7,8,9" | |
function stringReverse(s) { | |
if (!_.isString(s)) return undefined; | |
return s.split('').reverse().join(""); | |
} | |
stringReverse("abc"); | |
//=> "cba" | |
stringReverse(1); | |
//=> undefined | |
var rev = dispatch(invoker('reverse', Array.prototype.reverse), | |
stringReverse); | |
rev([1,2,3]); | |
//=> [3, 2, 1] | |
rev("abc"); | |
//=> "cba" | |
function performCommandHardcoded(command) { | |
var result; | |
switch (command.type) | |
{ | |
case 'notify': | |
result = notify(command.message); | |
break; | |
case 'join': | |
result = changeView(command.target); | |
break; | |
default: | |
alert(command.type); | |
} | |
return result; | |
} | |
function isa(type, action) { | |
return function(obj) { | |
if (type === obj.type) | |
return action(obj); | |
} | |
} | |
var performCommand = dispatch( | |
isa('notify', function(obj) { return notify(obj.message) }), | |
isa('join', function(obj) { return changeView(obj.target) }), | |
function(obj) { alert(obj.type) }); | |
var performAdminCommand = dispatch( | |
isa('kill', function(obj) { return shutdown(obj.hostname) }), | |
performCommand); | |
var performTrialUserCommand = dispatch( | |
isa('join', function(obj) { alert("Cannot join until approved") }), | |
performCommand); | |
function rightAwayInvoker() { | |
var args = _.toArray(arguments); | |
var method = args.shift(); | |
var target = args.shift(); | |
return method.apply(target, args); | |
} | |
rightAwayInvoker(Array.prototype.reverse, [1,2,3]) | |
//=> [3, 2, 1] | |
function leftCurryDiv(n) { | |
return function(d) { | |
return n/d; | |
}; | |
} | |
function rightCurryDiv(d) { | |
return function(n) { | |
return n/d; | |
}; | |
} | |
function curry(fun) { | |
return function(arg) { | |
return fun(arg); | |
}; | |
} | |
function curry2(fun) { | |
return function(secondArg) { | |
return function(firstArg) { | |
return fun(firstArg, secondArg); | |
}; | |
}; | |
} | |
function div(n, d) { return n / d } | |
var div10 = curry2(div)(10); | |
div10(50); | |
//=> 5 | |
var parseBinaryString = curry2(parseInt)(2); | |
parseBinaryString("111"); | |
//=> 7 | |
parseBinaryString("10"); | |
//=> 2 | |
var plays = [{artist: "Burial", track: "Archangel"}, | |
{artist: "Ben Frost", track: "Stomp"}, | |
{artist: "Ben Frost", track: "Stomp"}, | |
{artist: "Burial", track: "Archangel"}, | |
{artist: "Emeralds", track: "Snores"}, | |
{artist: "Burial", track: "Archangel"}]; | |
_.countBy(plays, function(song) { | |
return [song.artist, song.track].join(" - "); | |
}); | |
//=> {"Ben Frost - Stomp": 2, | |
// "Burial - Archangel": 3, | |
// "Emeralds - Snores": 1} | |
function songToString(song) { | |
return [song.artist, song.track].join(" - "); | |
} | |
var songCount = curry2(_.countBy)(songToString); | |
songCount(plays); | |
//=> {"Ben Frost - Stomp": 2, | |
// "Burial - Archangel": 3, | |
// "Emeralds - Snores": 1} | |
function curry3(fun) { | |
return function(last) { | |
return function(middle) { | |
return function(first) { | |
return fun(first, middle, last); | |
}; | |
}; | |
}; | |
}; | |
var songsPlayed = curry3(_.uniq)(false)(songToString); | |
songsPlayed(plays); | |
//=> [{artist: "Burial", track: "Archangel"}, | |
// {artist: "Ben Frost", track: "Stomp"}, | |
// {artist: "Emeralds", track: "Snores"}] | |
function toHex(n) { | |
var hex = n.toString(16); | |
return (hex.length < 2) ? [0, hex].join(''): hex; | |
} | |
function rgbToHexString(r, g, b) { | |
return ["#", toHex(r), toHex(g), toHex(b)].join(''); | |
} | |
rgbToHexString(255, 255, 255); | |
//=> "#ffffff" | |
var blueGreenish = curry3(rgbToHexString)(255)(200); | |
blueGreenish(0); | |
//=> "#00c8ff" | |
var greaterThan = curry2(function (lhs, rhs) { return lhs > rhs }); | |
var lessThan = curry2(function (lhs, rhs) { return lhs < rhs }); | |
var withinRange = checker( | |
validator("arg must be greater than 10", greaterThan(10)), | |
validator("arg must be less than 20", lessThan(20))); | |
function divPart(n) { | |
return function(d) { | |
return n / d; | |
}; | |
} | |
var over10Part = divPart(10); | |
over10Part(2); | |
//=> 5 | |
function partial1(fun, arg1) { | |
return function(/* args */) { | |
var args = construct(arg1, arguments); | |
return fun.apply(fun, args); | |
}; | |
} | |
function partial2(fun, arg1, arg2) { | |
return function(/* args */) { | |
var args = cat([arg1, arg2], arguments); | |
return fun.apply(fun, args); | |
}; | |
} | |
var div10By2 = partial2(div, 10, 2) | |
div10By2() | |
//=> 5 | |
function partial(fun /*, pargs */) { | |
var pargs = _.rest(arguments); | |
return function(/* arguments */) { | |
var args = cat(pargs, _.toArray(arguments)); | |
return fun.apply(fun, args); | |
}; | |
} | |
var zero = validator("cannot be zero", function(n) { return 0 === n | |
}); | |
var number = validator("arg must be a number", _.isNumber); | |
function sqr(n) { | |
if (!number(n)) throw new Error(number.message); | |
if (zero(n)) throw new Error(zero.message); | |
return n * n; | |
} | |
function condition1(/* validators */) { | |
var validators = _.toArray(arguments); | |
return function(fun, arg) { | |
var errors = mapcat(function(isValid) { | |
return isValid(arg) ? [] : [isValid.message]; | |
}, validators); | |
if (!_.isEmpty(errors)) | |
throw new Error(errors.join(", ")); | |
return fun(arg); | |
}; | |
} | |
var sqrPre = condition1( | |
validator("arg must not be zero", complement(zero)), | |
validator("arg must be a number", _.isNumber)); | |
function uncheckedSqr(n) { return n * n }; | |
uncheckedSqr(''); | |
//=> 0 | |
var checkedSqr = partial1(sqrPre, uncheckedSqr); | |
var sillySquare = partial1( | |
condition1(validator("should be even", isEven)), | |
checkedSqr); | |
var validateCommand = condition1( | |
validator("arg must be a map", _.isObject), | |
validator("arg must have the correct keys", hasKeys('msg', | |
'type'))); | |
var createCommand = partial(validateCommand, _.identity); | |
var createLaunchCommand = partial1( | |
condition1( | |
validator("arg must have the count down", hasKeys('countDown'))), | |
createCommand); | |
var isntString = _.compose(function(x) { return !x }, _.isString); | |
isntString([]); | |
//=> true | |
function not(x) { return !x } | |
var composedMapcat = _.compose(splat(cat), _.map); | |
composedMapcat([[1,2],[3,4],[5]], _.identity); | |
//=> [1, 2, 3, 4, 5] | |
var sqrPost = condition1( | |
validator("result should be a number", _.isNumber), | |
validator("result should not be zero", complement(zero)), | |
validator("result should be positive", greaterThan(0))); | |
var megaCheckedSqr = _.compose(partial(sqrPost, _.identity), | |
checkedSqr); | |
// Chap 6 | |
function myLength(ary) { | |
if (_.isEmpty(ary)) | |
return 0; | |
else | |
return 1 + myLength(_.rest(ary)); | |
} | |
function cycle(times, ary) { | |
if (times <= 0) | |
return []; | |
else | |
return cat(ary, cycle(times - 1, ary)); | |
} | |
var zipped1 = [['a', 1]]; | |
function constructPair(pair, rests) { | |
return [construct(_.first(pair), _.first(rests)), | |
construct(second(pair), second(rests))]; | |
} | |
constructPair(['a', 1], | |
constructPair(['b', 2], | |
constructPair(['c', 3], [[],[]]))); | |
//=> [['a','b','c'],[1,2,3]] | |
function unzip(pairs) { | |
if (_.isEmpty(pairs)) return [[],[]]; | |
return constructPair(_.first(pairs), unzip(_.rest(pairs))); | |
} | |
var influences = [ | |
['Lisp', 'Smalltalk'], | |
['Lisp', 'Scheme'], | |
['Smalltalk', 'Self'], | |
['Scheme', 'JavaScript'], | |
['Scheme', 'Lua'], | |
['Self', 'Lua'], | |
['Self', 'JavaScript']]; | |
function nexts(graph, node) { | |
if (_.isEmpty(graph)) return []; | |
var pair = _.first(graph); | |
var from = _.first(pair); | |
var to = second(pair); | |
var more = _.rest(graph); | |
if (_.isEqual(node, from)) | |
return construct(to, nexts(more, node)); | |
else | |
return nexts(more, node); | |
} | |
function depthSearch(graph, nodes, seen) { | |
if (_.isEmpty(nodes)) return rev(seen); | |
var node = _.first(nodes); | |
var more = _.rest(nodes); | |
if (_.contains(seen, node)) | |
return depthSearch(graph, more, seen); | |
else | |
return depthSearch(graph, | |
cat(nexts(graph, node), more), | |
construct(node, seen)); | |
} | |
function tcLength(ary, n) { | |
var l = n ? n : 0; | |
if (_.isEmpty(ary)) | |
return l; | |
else | |
return tcLength(_.rest(ary), l + 1); | |
} | |
tcLength(_.range(10)); | |
//=> 10 | |
function andify(/* preds */) { | |
var preds = _.toArray(arguments); | |
return function(/* args */) { | |
var args = _.toArray(arguments); | |
var everything = function(ps, truth) { | |
if (_.isEmpty(ps)) | |
return truth; | |
else | |
return _.every(args, _.first(ps)) | |
&& everything(_.rest(ps), truth); | |
}; | |
return everything(preds, true); | |
}; | |
} | |
function orify(/* preds */) { | |
var preds = _.toArray(arguments); | |
return function(/* args */) { | |
var args = _.toArray(arguments); | |
var something = function(ps, truth) { | |
if (_.isEmpty(ps)) | |
return truth; | |
else | |
return _.some(args, _.first(ps)) | |
|| something(_.rest(ps), truth); | |
}; | |
return something(preds, false); | |
}; | |
} | |
function evenSteven(n) { | |
if (n === 0) | |
return true; | |
else | |
return oddJohn(Math.abs(n) - 1); | |
} | |
function oddJohn(n) { | |
if (n === 0) | |
return false; | |
else | |
return evenSteven(Math.abs(n) - 1); | |
} | |
function flat(ary) { | |
if (_.isArray(ary)) | |
return cat.apply(cat, _.map(ary, flat)); | |
else | |
return [ary]; | |
} | |
function deepClone(obj) { | |
if (!existy(obj) || !_.isObject(obj)) | |
return obj; | |
var temp = new obj.constructor(); | |
for (var key in obj) | |
if (obj.hasOwnProperty(key)) | |
temp[key] = deepClone(obj[key]); | |
return temp; | |
} | |
function visit(mapFun, resultFun, ary) { | |
if (_.isArray(ary)) | |
return resultFun(_.map(ary, mapFun)); | |
else | |
return resultFun(ary); | |
} | |
function postDepth(fun, ary) { | |
return visit(partial1(postDepth, fun), fun, ary); | |
} | |
function preDepth(fun, ary) { | |
return visit(partial1(preDepth, fun), fun, fun(ary)); | |
} | |
function influencedWithStrategy(strategy, lang, graph) { | |
var results = []; | |
strategy(function(x) { | |
if (_.isArray(x) && _.first(x) === lang) | |
results.push(second(x)); | |
return x; | |
}, graph); | |
return results; | |
} | |
function evenOline(n) { | |
if (n === 0) | |
return true; | |
else | |
return partial1(oddOline, Math.abs(n) - 1); | |
} | |
function oddOline(n) { | |
if (n === 0) | |
return false; | |
else | |
return partial1(evenOline, Math.abs(n) - 1); | |
} | |
function trampoline(fun /*, args */) { | |
var result = fun.apply(fun, _.rest(arguments)); | |
while (_.isFunction(result)) { | |
result = result(); | |
} | |
return result; | |
} | |
function isEvenSafe(n) { | |
if (n === 0) | |
return true; | |
else | |
return trampoline(partial1(oddOline, Math.abs(n) - 1)); | |
} | |
function isOddSafe(n) { | |
if (n === 0) | |
return false; | |
else | |
return trampoline(partial1(evenOline, Math.abs(n) - 1)); | |
} | |
function generator(seed, current, step) { | |
return { | |
head: current(seed), | |
tail: function() { | |
console.log("forced"); | |
return generator(step(seed), current, step); | |
} | |
}; | |
} | |
function genHead(gen) { return gen.head } | |
function genTail(gen) { return gen.tail() } | |
var ints = generator(0, _.identity, function(n) { return n+1 }); | |
function genTake(n, gen) { | |
var doTake = function(x, g, ret) { | |
if (x === 0) | |
return ret; | |
else | |
return partial(doTake, x-1, genTail(g), cat(ret, genHead(g))); | |
}; | |
return trampoline(doTake, n, gen, []); | |
} | |
function asyncGetAny(interval, urls, onsuccess, onfailure) { | |
var n = urls.length; | |
var looper = function(i) { | |
setTimeout(function() { | |
if (i >= n) { | |
onfailure("failed"); | |
return; | |
} | |
$.get(urls[i], onsuccess) | |
.always(function() { console.log("try: " + urls[i]) }) | |
.fail(function() { | |
looper(i + 1); | |
}); | |
}, interval); | |
} | |
looper(0); | |
return "go"; | |
} | |
var groupFrom = curry2(_.groupBy)(_.first); | |
var groupTo = curry2(_.groupBy)(second); | |
function influenced(graph, node) { | |
return _.map(groupFrom(graph)[node], second); | |
} | |
// Chap 7 | |
var rand = partial1(_.random, 1); | |
function randString(len) { | |
var ascii = repeatedly(len, partial1(rand, 26)); | |
return _.map(ascii, function(n) { | |
return n.toString(36); | |
}).join(''); | |
} | |
PI = 3.14; | |
function areaOfACircle(radius) { | |
return PI * sqr(radius); | |
} | |
areaOfACircle(3); | |
//=> 28.26 | |
function generateRandomCharacter() { | |
return rand(26).toString(36); | |
} | |
function generateString(charGen, len) { | |
return repeatedly(len, charGen).join(''); | |
} | |
var composedRandomString = partial1(generateString, generateRandomCharacter); | |
composedRandomString(10); | |
//=> "j18obij1jc" | |
var a = [1, [10, 20, 30], 3]; | |
var secondTwice = _.compose(second, second); | |
second(a) === secondTwice(a); | |
//=> false | |
function skipTake(n, coll) { | |
var ret = []; | |
var sz = _.size(coll); | |
for(var index = 0; index < sz; index += n) { | |
ret.push(coll[index]); | |
} | |
return ret; | |
} | |
function summ(ary) { | |
var result = 0; | |
var sz = ary.length; | |
for (var i = 0; i < sz; i++) | |
result += ary[i]; | |
return result; | |
} | |
summ(_.range(1,11)); | |
//=> 55 | |
function summRec(ary, seed) { | |
if (_.isEmpty(ary)) | |
return seed; | |
else | |
return summRec(_.rest(ary), _.first(ary) + seed); | |
} | |
summRec([], 0); | |
//=> 0 | |
summRec(_.range(1,11), 0); | |
//=> 55 | |
function deepFreeze(obj) { | |
if (!Object.isFrozen(obj)) | |
Object.freeze(obj); | |
for (var key in obj) { | |
if (!obj.hasOwnProperty(key) || !_.isObject(obj[key])) | |
continue; | |
deepFreeze(obj[key]); | |
} | |
} | |
var freq = curry2(_.countBy)(_.identity); | |
function merge(/*args*/) { | |
return _.extend.apply(null, construct({}, arguments)); | |
} | |
function Point(x, y) { | |
this._x = x; | |
this._y = y; | |
} | |
Point.prototype = { | |
withX: function(val) { | |
return new Point(val, this._y); | |
}, | |
withY: function(val) { | |
return new Point(this._x, val); | |
} | |
}; | |
function Queue(elems) { | |
this._q = elems; | |
} | |
Queue.prototype = { | |
enqueue: function(thing) { | |
return new Queue(cat(this._q, [thing])); | |
} | |
}; | |
var SaferQueue = function(elems) { | |
this._q = _.clone(elems); | |
} | |
SaferQueue.prototype = { | |
enqueue: function(thing) { | |
return new SaferQueue(cat(this._q, [thing])); | |
} | |
}; | |
function queue() { | |
return new SaferQueue(_.toArray(arguments)); | |
} | |
var q = queue(1,2,3); | |
var enqueue = invoker('enqueue', SaferQueue.prototype.enqueue); | |
enqueue(q, 42); | |
//=> {_q: [1, 2, 3, 42]} | |
function Container(init) { | |
this._value = init; | |
}; | |
Container.prototype = { | |
update: function(fun /*, args */) { | |
var args = _.rest(arguments); | |
var oldValue = this._value; | |
this._value = fun.apply(this, construct(oldValue, args)); | |
return this._value; | |
} | |
}; | |
var aNumber = new Container(42); | |
aNumber.update(function(n) { return n + 1 }); | |
//=> 43 | |
aNumber; | |
//=> {_value: 43} | |
// Chap 8 | |
function createPerson() { | |
var firstName = ""; | |
var lastName = ""; | |
var age = 0; | |
return { | |
setFirstName: function(fn) { | |
firstName = fn; | |
return this; | |
}, | |
setLastName: function(ln) { | |
lastName = ln; | |
return this; | |
}, | |
setAge: function(a) { | |
age = a; | |
return this; | |
}, | |
toString: function() { | |
return [firstName, lastName, age].join(' '); | |
} | |
}; | |
} | |
createPerson() | |
.setFirstName("Mike") | |
.setLastName("Fogus") | |
.setAge(108) | |
.toString(); | |
//=> "Mike Fogus 108" | |
var TITLE_KEY = 'titel'; | |
// ... a whole bunch of code later | |
_.chain(library) | |
.pluck(TITLE_KEY) | |
.sort() | |
.value(); | |
//=> [undefined, undefined, undefined] | |
function LazyChain(obj) { | |
this._calls = []; | |
this._target = obj; | |
} | |
LazyChain.prototype.invoke = function(methodName /*, args */) { | |
var args = _.rest(arguments); | |
this._calls.push(function(target) { | |
var meth = target[methodName]; | |
return meth.apply(target, args); | |
}); | |
return this; | |
}; | |
LazyChain.prototype.force = function() { | |
return _.reduce(this._calls, function(target, thunk) { | |
return thunk(target); | |
}, this._target); | |
}; | |
LazyChain.prototype.tap = function(fun) { | |
this._calls.push(function(target) { | |
fun(target); | |
return target; | |
}); | |
return this; | |
} | |
function LazyChainChainChain(obj) { | |
var isLC = (obj instanceof LazyChain); | |
this._calls = isLC ? cat(obj._calls, []) : []; | |
this._target = isLC ? obj._target : obj; | |
} | |
LazyChainChainChain.prototype = LazyChain.prototype; | |
var longing = $.Deferred(); | |
function go() { | |
var d = $.Deferred(); | |
$.when("") | |
.then(function() { | |
setTimeout(function() { | |
console.log("sub-task 1"); | |
}, 5000) | |
}) | |
.then(function() { | |
setTimeout(function() { | |
console.log("sub-task 2"); | |
}, 10000) | |
}) | |
.then(function() { | |
setTimeout(function() { | |
d.resolve("done done done done"); | |
}, 15000) | |
}) | |
return d.promise(); | |
} | |
function pipeline(seed /*, args */) { | |
return _.reduce(_.rest(arguments), | |
function(l,r) { return r(l); }, | |
seed); | |
}; | |
function fifth(a) { | |
return pipeline(a | |
, _.rest | |
, _.rest | |
, _.rest | |
, _.rest | |
, _.first); | |
} | |
function negativeFifth(a) { | |
return pipeline(a | |
, fifth | |
, function(n) { return -n }); | |
} | |
negativeFifth([1,2,3,4,5,6,7,8,9]); | |
//=> -5 | |
function firstEditions(table) { | |
return pipeline(table | |
, function(t) { return as(t, {ed: 'edition'}) } | |
, function(t) { return project(t, ['title', 'edition', 'isbn']) } | |
, function(t) { return restrict(t, function(book) { | |
return book.edition === 1; | |
}); | |
}); | |
} | |
var RQL = { | |
select: curry2(project), | |
as: curry2(as), | |
where: curry2(restrict) | |
}; | |
function allFirstEditions(table) { | |
return pipeline(table | |
, RQL.as({ed: 'edition'}) | |
, RQL.select(['title', 'edition', 'isbn']) | |
, RQL.where(function(book) { | |
return book.edition === 1; | |
})); | |
} | |
function actions(acts, done) { | |
return function (seed) { | |
var init = { values: [], state: seed }; | |
var intermediate = _.reduce(acts, function (stateObj, action) { | |
var result = action(stateObj.state); | |
var values = cat(stateObj.values, [result.answer]); | |
return { values: values, state: result.state }; | |
}, init); | |
var keep = _.filter(intermediate.values, existy); | |
return done(keep, intermediate.state); | |
}; | |
}; | |
function mSqr() { | |
return function(state) { | |
var ans = sqr(state); | |
return {answer: ans, state: ans}; | |
} | |
} | |
var doubleSquareAction = actions( | |
[mSqr(), | |
mSqr()], | |
function(values) { | |
return values; | |
}); | |
doubleSquareAction(10); | |
//=> [100, 10000] | |
function mNote() { | |
return function(state) { | |
note(state); | |
return {answer: undefined, state: state}; | |
} | |
} | |
function mNeg() { | |
return function(state) { | |
return {answer: -state, state: -state}; | |
} | |
} | |
var negativeSqrAction = actions([mSqr(), mNote(), mNeg()], | |
function(_, state) { | |
return state; | |
}); | |
function lift(answerFun, stateFun) { | |
return function(/* args */) { | |
var args = _.toArray(arguments); | |
return function(state) { | |
var ans = answerFun.apply(null, construct(state, args)); | |
var s = stateFun ? stateFun(state) : ans; | |
return {answer: ans, state: s}; | |
}; | |
}; | |
}; | |
var mSqr2 = lift(sqr); | |
var mNote2 = lift(note, _.identity); | |
var mNeg2 = lift(function(n) { return -n }); | |
var negativeSqrAction2 = actions([mSqr2(), mNote2(), mNeg2()], | |
function(_, state) { | |
return state; | |
}); | |
var push = lift(function(stack, e) { return construct(e, stack) }); | |
var pop = lift(_.first, _.rest); | |
var stackAction = actions([ | |
push(1), | |
push(2), | |
pop() | |
], | |
function(values, state) { | |
return values; | |
}); | |
// Chap 9 | |
function lazyChain(obj) { | |
var calls = []; | |
return { | |
invoke: function(methodName /* args */) { | |
var args = _.rest(arguments); | |
calls.push(function(target) { | |
var meth = target[methodName]; | |
return meth.apply(target, args); | |
}); | |
return this; | |
}, | |
force: function() { | |
return _.reduce(calls, function(ret, thunk) { | |
return thunk(ret); | |
}, obj); | |
} | |
}; | |
} | |
function deferredSort(ary) { | |
return lazyChain(ary).invoke('sort'); | |
} | |
var deferredSorts = _.map([[2,1,3], [7,7,1], [0,9,5]], deferredSort); | |
//=> [<thunk>, <thunk>, <thunk>] | |
function force(thunk) { | |
return thunk.force(); | |
} | |
var validateTriples = validator( | |
"Each array should have three elements", | |
function (arrays) { | |
return _.every(arrays, function(a) { | |
return a.length === 3; | |
}); | |
}); | |
var validateTripleStore = partial1(condition1(validateTriples), _.identity); | |
function postProcess(arrays) { | |
return _.map(arrays, second); | |
} | |
function processTriples(data) { | |
return pipeline(data | |
, JSON.parse | |
, validateTripleStore | |
, deferredSort | |
, force | |
, postProcess | |
, invoker('sort', Array.prototype.sort) | |
, str); | |
} | |
var reportDataPackets = _.compose( | |
function(s) { $('#result').text(s) }, | |
processTriples); | |
function polyToString(obj) { | |
if (obj instanceof String) | |
return obj; | |
else if (obj instanceof Array) | |
return stringifyArray(obj); | |
return obj.toString(); | |
} | |
function stringifyArray(ary) { | |
return ["[", _.map(ary, polyToString).join(","), "]"].join(''); | |
} | |
var polyToString = dispatch( | |
function(s) { return _.isString(s) ? s : undefined }, | |
function(s) { return _.isArray(s) ? stringifyArray(s) : undefined }, | |
function(s) { return _.isObject(s) ? JSON.stringify(s) : undefined }, | |
function(s) { return s.toString() }); | |
Container.prototype.toString = function() { | |
return ["@<", polyToString(this._value), ">"].join(''); | |
} | |
function ContainerClass() {} | |
function ObservedContainerClass() {} | |
function HoleClass() {} | |
function CASClass() {} | |
function TableBaseClass() {} | |
ObservedContainerClass.prototype = new ContainerClass(); | |
HoleClass.prototype = new ObservedContainerClass(); | |
CASClass.prototype = new HoleClass(); | |
TableBaseClass.prototype = new HoleClass(); | |
var ContainerClass = Class.extend({ | |
init: function(val) { | |
this._value = val; | |
}, | |
}); | |
var c = new ContainerClass(42); | |
c; | |
//=> {_value: 42 ...} | |
c instanceof Class; | |
//=> true | |
var ObservedContainerClass = ContainerClass.extend({ | |
observe: function(f) { note("set observer") }, | |
notify: function() { note("notifying observers") } | |
}); | |
var HoleClass = ObservedContainerClass.extend({ | |
init: function(val) { this.setValue(val) }, | |
setValue: function(val) { | |
this._value = val; | |
this.notify(); | |
return val; | |
} | |
}); | |
var CASClass = HoleClass.extend({ | |
swap: function(oldVal, newVal) { | |
if (!_.isEqual(oldVal, this._value)) fail("No match"); | |
return this.setValue(newVal); | |
} | |
}); | |
function Container(val) { | |
this._value = val; | |
this.init(val); | |
} | |
Container.prototype.init = _.identity; | |
var HoleMixin = { | |
setValue: function(newValue) { | |
var oldVal = this._value; | |
this.validate(newValue); | |
this._value = newValue; | |
this.notify(oldVal, newValue); | |
return this._value; | |
} | |
}; | |
var Hole = function(val) { | |
Container.call(this, val); | |
} | |
var ObserverMixin = (function() { | |
var _watchers = []; | |
return { | |
watch: function(fun) { | |
_watchers.push(fun); | |
return _.size(_watchers); | |
}, | |
notify: function(oldVal, newVal) { | |
_.each(_watchers, function(watcher) { | |
watcher.call(this, oldVal, newVal); | |
}); | |
return _.size(_watchers); | |
} | |
}; | |
}()); | |
var ValidateMixin = { | |
addValidator: function(fun) { | |
this._validator = fun; | |
}, | |
init: function(val) { | |
this.validate(val); | |
}, | |
validate: function(val) { | |
if (existy(this._validator) && | |
!this._validator(val)) | |
fail("Attempted to set invalid value " + polyToString(val)); | |
} | |
}; | |
_.extend(Hole.prototype | |
, HoleMixin | |
, ValidateMixin | |
, ObserverMixin); | |
var SwapMixin = { | |
swap: function(fun /* , args... */) { | |
var args = _.rest(arguments) | |
var newValue = fun.apply(this, construct(this._value, args)); | |
return this.setValue(newValue); | |
} | |
}; | |
var SnapshotMixin = { | |
snapshot: function() { | |
return deepClone(this._value); | |
} | |
}; | |
_.extend(Hole.prototype | |
, HoleMixin | |
, ValidateMixin | |
, ObserverMixin | |
, SwapMixin | |
, SnapshotMixin); | |
var CAS = function(val) { | |
Hole.call(this, val); | |
} | |
var CASMixin = { | |
swap: function(oldVal, f) { | |
if (this._value === oldVal) { | |
this.setValue(f(this._value)); | |
return this._value; | |
} | |
else { | |
return undefined; | |
} | |
} | |
}; | |
_.extend(CAS.prototype | |
, HoleMixin | |
, ValidateMixin | |
, ObserverMixin | |
, SwapMixin | |
, CASMixin | |
, SnapshotMixin); | |
function contain(value) { | |
return new Container(value); | |
} | |
function hole(val /*, validator */) { | |
var h = new Hole(); | |
var v = _.toArray(arguments)[1]; | |
if (v) h.addValidator(v); | |
h.setValue(val); | |
return h; | |
} | |
var swap = invoker('swap', Hole.prototype.swap); | |
function cas(val /*, args */) { | |
var h = hole.apply(this, arguments); | |
var c = new CAS(val); | |
c._validator = h._validator; | |
return c; | |
} | |
var compareAndSwap = invoker('swap', CAS.prototype.swap); | |
function snapshot(o) { return o.snapshot() } | |
function addWatcher(o, fun) { o.watch(fun) } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment