Skip to content

Instantly share code, notes, and snippets.

@luislee818
Created January 4, 2016 09:32
Show Gist options
  • Select an option

  • Save luislee818/9d259f50eda997bb0344 to your computer and use it in GitHub Desktop.

Select an option

Save luislee818/9d259f50eda997bb0344 to your computer and use it in GitHub Desktop.

Revisions

  1. Dapeng Li created this gist Jan 4, 2016.
    1,977 changes: 1,977 additions & 0 deletions funjs.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1977 @@
    // 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) }