Skip to content

Instantly share code, notes, and snippets.

@JoelCodes
Created June 26, 2017 23:04
Show Gist options
  • Select an option

  • Save JoelCodes/e5ec1bee606b27e90b73b02be4ea98d4 to your computer and use it in GitHub Desktop.

Select an option

Save JoelCodes/e5ec1bee606b27e90b73b02be4ea98d4 to your computer and use it in GitHub Desktop.
{
"extends": "airbnb-base",
"plugins": [
"import"
]
}
const { fromArray, stream } = require('./lazy');
const lFA = fromArray(['Joel', 'Sam', 'Don', 'David']);
const changed = lFA
.filter(name => name[0] !== 'D')
.map(name => name.toUpperCase());
console.log('Running For Each');
changed.forEach((val) => {
console.log('Hello,', val);
});
const streamFrom0 = stream();
streamFrom0
.take(50)
.skip(5)
.forEach((val, index) => {
console.log(val, index);
});
const tenDown = stream(10, val => val - 1).take(10);
const fiveUp = stream().take(5);
tenDown.zip(fiveUp).forEach((vals) => {
console.log(vals);
});
const nums = fromArray([4, 6, 2, 1, 5]);
const numsOver2 = nums
.filter(val => val > 2);
console.log('Now To Do Something...');
console.log(numsOver2.reduce((sum, num) => sum + num, 0));
const Nil = {
head: undefined,
tail: undefined,
count: 0,
reduce: (reducer, seed) => seed,
any: () => false,
all: () => true,
first: () => undefined,
concat: listFn => listFn(),
toArray: () => [],
at: () => undefined,
forEach: () => {},
};
[
'filter', 'map',
'take', 'takeUntil', 'takeWhile',
'skip', 'skipUntil', 'skipWhile',
'flatMap', 'zip']
.forEach((methodName) => {
Nil[methodName] = () => Nil;
});
Object.freeze(Nil);
const Cons = (head, tail = () => Nil) => {
const list = {
head,
reduce: (reducer, seed) => tail().reduce(reducer, reducer(seed, head)),
filter: predicate => (predicate(head)
? Cons(head, () => tail().filter(predicate))
: tail().filter(predicate)),
toArray: () => [head].concat(tail().toArray()),
map: transformer => Cons(transformer(head), () => tail().map(transformer)),
all: predicate => predicate(head) && tail().all(predicate),
any: predicate => predicate(head) || tail().any(predicate),
take: amt => (isNaN(amt) || amt <= 0
? Nil
: Cons(head, () => tail().take(amt - 1))),
takeUntil: predicate => (predicate(head)
? Nil
: Cons(head, () => tail().takeUntil(predicate))),
takeWhile: predicate => (predicate(head)
? Cons(head, () => tail().takeWhile(predicate))
: Nil),
skip: amt => (isNaN(amt) || amt <= 0
? list
: tail().skip(amt - 1)),
skipUntil: predicate => (predicate(head)
? list
: tail().skipUntil(predicate)),
skipWhile: predicate => (predicate(head)
? tail().skipWhile(predicate)
: list),
first: predicate => (predicate(head)
? head
: tail().first(predicate)),
concat: tailFn => Cons(head, () => tail().concat(tailFn)),
zip: otherList => (otherList === Nil
? Nil
: Cons([head, otherList.head], () => tail().zip(otherList.tail))),
flatMap: transformer => transformer(head)
.concat(() => tail().flatMap(transformer)),
at: index => (isNaN(index) || index <= 0
? head
: tail().at(index - 1)),
forEach: (action, index = 0) => {
action(head, index);
tail().forEach(action, index + 1);
},
};
Object.defineProperties(list, {
tail: { get: tail },
count: {
get: () => 1 + tail().count,
},
});
return Object.freeze(list);
};
const fromArray = (array) => {
if (!array || !array.length) {
return Nil;
}
return Cons(array[0], () => fromArray(array.slice(1)));
};
const stream = (start = 0, incrementer = val => val + 1) =>
Cons(start, () => stream(incrementer(start), incrementer));
module.exports = {
Nil,
Cons,
fromArray,
stream,
};
{
"name": "lazy",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "mocha --reporter nyan",
"docs": "mocha --reporter markdown > doc.md",
"lint": "node_modules/.bin/eslint *.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"eslint": "^3.19.0",
"eslint-config-airbnb": "^15.0.1",
"eslint-config-airbnb-base": "^11.2.0",
"eslint-config-google": "^0.8.0",
"eslint-plugin-import": "^2.6.0",
"eslint-plugin-jsx-a11y": "^5.0.3",
"eslint-plugin-react": "^7.1.0"
},
"dependencies": {
"chai": "^4.0.2",
"mocha": "^3.4.2"
}
}
/* eslint-env mocha */
/* eslint no-unused-expressions:0 */
const { expect } = require('chai');
const { Nil, Cons, fromArray, stream } = require('./lazy');
describe('Nil', () => {
it('has undefined, immutable head and tail properties and a count of 0', () => {
expect(Nil).to.haveOwnProperty('head');
Nil.head = 3;
expect(Nil.head).to.be.undefined;
expect(Nil).to.haveOwnProperty('tail');
Nil.tail = 3;
expect(Nil.tail).to.be.undefined;
Nil.count = 3;
expect(Nil.count).to.eq(0);
});
describe('#toArray():ListType[]', () => {
expect(Nil.toArray()).to.deep.eq([]);
});
describe('#reduce(reducer:(AggType, ListType) => AggType, seed:AggType):AggType', () => {
it('returns the seed', () => {
expect(Nil.reduce((val, item) => val + item, 8)).to.eq(8);
});
});
describe('#filter(predicate: (any) => bool):List', () => {
it('returns Nil', () => {
expect(Nil.filter(() => true)).to.eq(Nil);
});
});
describe('#map(transformer: (any) => any):List', () => {
it('returns Nil', () => {
expect(Nil.map(a => a + 1)).to.eq(Nil);
});
});
describe('#any(predicate:(ListType) => bool):bool', () => {
it('returns false', () => {
expect(Nil.any(() => true)).to.be.false;
});
});
describe('#all(predicate:(ListType) => bool):bool', () => {
it('returns true', () => {
expect(Nil.all(() => true)).to.be.true;
});
});
describe('#take(amount:Int):List', () => {
it('returns Nil', () => {
expect(Nil.take(4)).to.eq(Nil);
});
});
describe('#takeWhile(predicate:(ListType) => bool):List', () => {
it('returns Nil', () => {
expect(Nil.takeWhile(() => true)).to.eq(Nil);
});
});
describe('#takeUntil(predicate:(ListType) => bool):List', () => {
it('returns Nil', () => {
expect(Nil.takeUntil(() => false)).to.eq(Nil);
});
});
describe('#skip(amount:int):List', () => {
it('returns Nil', () => {
expect(Nil.skip(4)).to.eq(Nil);
});
});
describe('#skipWhile(predicate:(any) => bool):List', () => {
it('returns Nil', () => {
expect(Nil.skipWhile(() => false)).to.eq(Nil);
});
});
describe('#skipUntil(predicate:(any) => bool):List', () => {
it('returns Nil', () => {
expect(Nil.skipUntil(() => true)).to.eq(Nil);
});
});
describe('#first(predicate:(any) => bool):any', () => {
it('returns undefined', () => {
expect(Nil.first(() => true)).to.be.undefined;
});
});
describe('#concat(() => List):', () => {
it('returns the new list', () => {
const newList = Cons(3);
expect(Nil.concat(() => newList)).to.eq(newList);
});
});
describe('#zip(List)', () => {
it('returns Nil', () => {
expect(Nil.zip(Cons(3))).to.eq(Nil);
});
});
describe('#flatMap((item) => List): List', () => {
it('returns Nil', () => {
expect(Nil.flatMap(() => Cons)).to.eq(Nil);
});
});
describe('#at(index):any', () => {
it('returns undefined', () => {
expect(Nil.at(0)).to.be.undefined;
});
});
describe('#forEach((val) => void):void', () => {
const action = () => { throw new Error('This Should Not Have Run'); };
Nil.forEach(action);
});
});
describe('Cons(head: any, tail: () => List)', () => {
it('has set, immutable head and tail properties and an appropriate count', () => {
const list = Cons(3, () => Nil);
expect(list).to.haveOwnProperty('head');
list.head = 4;
expect(list.head).to.eq(3);
expect(list).to.haveOwnProperty('tail');
list.tail = 3;
expect(list.tail).to.eq(Nil);
expect(list).to.haveOwnProperty('count');
list.count = 2;
expect(list.count).to.eq(1);
expect(Cons(3, () => Cons(4)).count).to.eq(2);
});
describe('#toArray()', () => {
expect(Cons(3, () => Cons(4)).toArray()).to.deep.eq([3, 4]);
});
describe('#reduce(reducer, seed)', () => {
it('returns the reduced value', () => {
const list = Cons(3, () => Cons(4));
expect(list.reduce((val, item) => val * item, 2)).to.eq(24);
});
});
describe('#filter(predicate)', () => {
it('returns an empty list if none match', () => {
expect(Cons(3, () => Cons(4)).filter(() => false)).to.eq(Nil);
});
it('returns a shortened list', () => {
const list = Cons(3, () => Cons(4, () => Cons(5)))
.filter(val => val !== 4);
expect(list.toArray()).to.deep.eq([3, 5]);
});
});
describe('#map(transformer)', () => {
it('returns a transformed list', () => {
expect(Cons(3, () => Cons(4)).map(val => -val).toArray()).to.deep.eq([-3, -4]);
});
});
describe('#all(predicate)', () => {
it('returns true if all are true', () => {
expect(Cons(4, () => Cons(3)).all(() => true)).to.be.true;
});
it('returns false if any fail the predicate', () => {
expect(Cons(4, () => Cons(3)).all(val => val !== 3)).to.be.false;
});
});
describe('#any(predicate', () => {
it('returns false if all are false', () => {
expect(Cons(4, () => Cons(3)).any(() => false)).to.be.false;
});
it('returns true if any pass', () => {
expect(Cons(4, () => Cons(3)).any(val => val === 3)).to.be.true;
});
});
describe('#take(amount)', () => {
it('returns Nil with #take(0)', () => {
const list = Cons(4, () => Cons(3));
expect(list.take(0)).to.eq(Nil);
});
it('returns a list with the appropriate items', () => {
const list = Cons(4, () => Cons(3));
expect(list.take(3).toArray()).to.deep.eq([4, 3]);
expect(list.take(1).toArray()).to.deep.eq([4]);
});
});
describe('#takeUntil(predicate)', () => {
it('returns Nil with true predicate', () => {
expect(Cons(4).takeUntil(() => true)).to.eq(Nil);
});
it('returns a list with the appropriate items', () => {
const list = Cons(4, () => Cons(3));
expect(list.takeUntil(() => false).toArray()).to.deep.eq([4, 3]);
expect(list.takeUntil(val => val === 3).toArray()).to.deep.eq([4]);
});
});
describe('#takeWhile', () => {
it('returns Nil with false', () => {
expect(Cons(4).takeWhile(() => false)).to.eq(Nil);
});
it('returns a list with the appropriate items', () => {
const list = Cons(4, () => Cons(3));
expect(list.takeWhile(() => true).toArray()).to.deep.eq([4, 3]);
expect(list.takeWhile(val => val !== 3).toArray()).to.deep.eq([4]);
});
});
describe('#skip(amt)', () => {
it('returns the same list with #skip(0)', () => {
const list = Cons(4, () => Cons(3));
expect(list.skip(0)).to.eq(list);
});
it('returns a list with the appropriate items', () => {
const list = Cons(4, () => Cons(3));
expect(list.skip(1).toArray()).to.deep.eq([3]);
expect(list.skip(2)).to.eq(Nil);
expect(list.skip(3)).to.eq(Nil);
});
});
describe('#skipUntil(predicate)', () => {
it('returns the same list with false', () => {
const list = Cons(4, () => Cons(3));
expect(list.skipUntil(() => true)).to.eq(list);
});
it('returns a list with the appropriate items', () => {
const list = Cons(4, () => Cons(3));
expect(list.skipUntil(() => false)).to.eq(Nil);
expect(list.skipUntil(val => val === 3).toArray()).to.deep.eq([3]);
});
});
describe('#skipUntil(predicate)', () => {
it('returns the same list with true', () => {
const list = Cons(4, () => Cons(3));
expect(list.skipUntil(() => true)).to.eq(list);
});
it('returns a list with the appropriate items', () => {
const list = Cons(4, () => Cons(3));
expect(list.skipUntil(() => false)).to.eq(Nil);
expect(list.skipUntil(val => val === 3).toArray()).to.deep.eq([3]);
});
});
describe('#skipWhile(predicate)', () => {
it('returns the sameList with a false', () => {
const list = Cons(4, () => Cons(3));
expect(list.skipWhile(() => false)).to.eq(list);
});
it('returns a list with the appropriate items', () => {
const list = Cons(4, () => Cons(3));
expect(list.skipWhile(() => true)).to.eq(Nil);
expect(list.skipWhile(val => val !== 3).toArray()).to.deep.eq([3]);
});
});
describe('#first(predicate)', () => {
it('returns the first matching value', () => {
const list = Cons(4, () => Cons(3));
expect(list.first(val => val === 4)).to.eq(4);
expect(list.first(val => val === 3)).to.eq(3);
});
});
describe('#concat(tailFn)', () => {
it('returns a new list with the attached tail', () => {
expect(Cons(4).concat(() => Cons(3)).toArray()).to.deep.eq([4, 3]);
});
});
describe('#zip(List):List<array>', () => {
it('returns Nil if Nil is passed in', () => {
expect(Cons(4).zip(Nil)).to.eq(Nil);
});
it('returns a new lazy evaluated list of pairs', () => {
const tailExpr = () => {
throw new Error('If this evaluated, lazy eval failed.');
};
expect(Cons(3, tailExpr).zip(Cons(4, tailExpr)).head).to.deep.eq([3, 4]);
});
});
describe('#flatMap((val) => List):List', () => {
it('creates a flat map of items', () => {
const list = Cons(3, () => Cons(4));
expect(list.flatMap(() => Nil)).to.eq(Nil);
expect(list.flatMap(val => Cons(val, () => Cons(val))).toArray())
.to.deep.eq([3, 3, 4, 4]);
});
});
describe('#at(index)', () => {
it('returns the appropriate item in a list', () => {
const list = Cons(3, () => Cons(4));
expect(list.at(0)).to.eq(3);
expect(list.at(1)).to.eq(4);
expect(list.at(2)).to.be.undefined;
});
});
describe('#forEach(action:(any, int) => void):any', () => {
it('runs an action on each item in a list', () => {
const items = [];
const indices = [];
Cons(3, () => Cons(4)).forEach((val, index) => {
items.push(val);
indices.push(index);
});
expect(items).to.deep.eq([3, 4]);
expect(indices).to.deep.eq([0, 1]);
});
});
});
describe('#fromArray(array) => List', () => {
it('returns Nil with an empty array', () => {
expect(fromArray([])).to.eq(Nil);
});
it('returns a List with the appropriate members', () => {
expect(fromArray([0]).toArray()).to.deep.eq([0]);
expect(fromArray([3, 4]).toArray()).to.deep.eq([3, 4]);
});
});
describe('#stream(start:int = 0, incrementer:((any) => any) = val => val + 1)', () => {
it('returns a list that starts with the start and increments according to the incrementer', () => {
const streamBackFrom2 = stream(2, val => val - 1);
expect(streamBackFrom2.head).to.eq(2);
expect(streamBackFrom2.at(1)).to.eq(1);
expect(streamBackFrom2.at(2)).to.eq(0);
});
it('defaults the incrementer to val => val + 1', () => {
const streamFrom2 = stream(2);
expect(streamFrom2.head).to.eq(2);
expect(streamFrom2.at(1)).to.eq(3);
expect(streamFrom2.at(2)).to.eq(4);
});
it('defaults the start to 0', () => {
const streamFrom0 = stream();
expect(streamFrom0.head).to.eq(0);
expect(streamFrom0.at(1)).to.eq(1);
expect(streamFrom0.at(2)).to.eq(2);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment