From 1472aacc6be92ade891ccfabbecb47cd1464ba32 Mon Sep 17 00:00:00 2001 From: Barney Carroll Date: Mon, 20 Feb 2017 17:44:09 +0000 Subject: [PATCH 1/4] Make functify.js CJS-compatible --- src/functify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/functify.js b/src/functify.js index 6ec5b07..f324e76 100644 --- a/src/functify.js +++ b/src/functify.js @@ -404,4 +404,4 @@ functify.fromGenerator = Functified.fromGenerator; functify.range = Functified.range; functify.zip = Functified.zip; -export default functify; +if(typeof module != 'undefined') module.exports = functify; From f1694d8b71fd9e3225b21a49341f33cd784ef11a Mon Sep 17 00:00:00 2001 From: Barney Carroll Date: Mon, 20 Feb 2017 17:45:10 +0000 Subject: [PATCH 2/4] Make functify.mjs ESM-compatible --- src/functify.mjs | 407 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 src/functify.mjs diff --git a/src/functify.mjs b/src/functify.mjs new file mode 100644 index 0000000..6ec5b07 --- /dev/null +++ b/src/functify.mjs @@ -0,0 +1,407 @@ +"use strict"; + +class Functified { + + constructor(iterable) { + // avoid re-wrapping iterables that have already been Functified + if (iterable.isFunctified) { + return iterable; + } + this.iterable = iterable; + this.isFunctified = true; + } + + *[Symbol.iterator]() { + for (let value of this.iterable) { + yield value; + } + } + + // fn(iterable) -> generator function + custom(fn) { + return Functified.fromGenerator(fn(this.iterable)); + } + + // alias dedupe, unique + distinct() { + var iterable = this.iterable; + var memory = new Set(); + return Functified.fromGenerator(function* () { + for (let value of iterable) { + if (!memory.has(value)) { + memory.add(value); + yield value; + } + } + }); + } + + filter(callback) { + var iterable = this.iterable; + return Functified.fromGenerator(function* () { + for (let value of iterable) { + if (callback(value)) { + yield value; + } + } + }); + } + + flatten() { + var iterable = this.iterable; + return Functified.fromGenerator(function* () { + for (let value of iterable) { + if (value[Symbol.iterator]) { + yield* functify(value).flatten(); + } else { + yield value; + } + } + }); + } + + groupBy(...predicates) { + return functify(predicates.map(fn => this.filter(fn))); + } + + groupByMap(map) { + return functify(map).map(([name, fn]) => [name, this.filter(fn)]); + } + + repeat(n = 1) { + var iterable = this.iterable; + return Functified.fromGenerator(function* () { + var i = 0; + while (i++ < n) { + for (let value of iterable) { + yield value; + } + } + }); + } + + // alias for repeat + loop(n = 1) { + console.warn("deprecating loop(n), use repeat(n) instead"); + return this.repeat(n); + } + + map(callback) { + var iterable = this.iterable; + return Functified.fromGenerator(function* () { + for (let value of iterable) { + yield callback(value); + } + }); + } + + skip(n) { + var iterable = this.iterable; + return Functified.fromGenerator(function* () { + var i = 0; + for (let value of iterable) { + if (i < n) { + i++; + } else { + yield value; + } + } + }); + } + + skipWhile(predicate) { + var iterable = this.iterable; + return Functified.fromGenerator(function* () { + var skip = true; + for (let value of iterable) { + if (!predicate(value)) { + skip = false; + } + if (!skip) { + yield value; + } + } + }); + } + + take(n) { + // using an explicit iterator supports pausable iteratables + var iterator = this.iterable[Symbol.iterator](); + var self = this; + return Functified.fromGenerator(function* () { + let i = 0; + if (self.hasOwnProperty("startValue") && self.isPausable) { + yield self.startValue; + i++; + } + while (i < n) { + var result = iterator.next(); + if (result.done) { + break; + } else { + yield result.value; + i++; + } + } + }); + } + + takeUntil(predicate) { + var iterator = this.iterable[Symbol.iterator](); + var self = this; + return Functified.fromGenerator(function* () { + if (self.hasOwnProperty("startValue") && self.isPausable) { + yield self.startValue; + } + while (true) { + var result = iterator.next(); + if (result.done) { + break; + } else { + if (predicate(result.value)) { + // save the value so we can yield if takeUntil is called again + self.startValue = result.value; + break; + } else { + yield result.value; + } + } + } + }); + } + + enumerate(start = 0) { + var iterable = this.iterable; + return Functified.fromGenerator(function* () { + let i = start; + for (let value of iterable) { + yield [i++, value]; + } + }); + } + + zip() { + return Functified.zip(this.iterable); + } + + // reducing functions + every(callback) { + for (let value of this.iterable) { + if (!callback(value)) { + return false; + } + } + return true; + } + + reduce(callback, initialValue) { + let accum = initialValue; + let iterator = this.iterable[Symbol.iterator](); + + if (accum === undefined) { + let result = iterator.next(); + if (result.done) { + throw "not enough values to reduce"; + } else { + accum = result.value; + } + } + + while (true) { + let result = iterator.next(); + if (result.done) { + break; + } else { + accum = callback(accum, result.value); + } + } + + return accum; + } + + some(callback) { + for (let value of this.iterable) { + if (callback(value)) { + return true; + } + } + return false; + } + + entries() { + if (this.iterable.entries) { + return new Functified(this.iterable.entries()); + } else { + throw "doesn't have entries"; + } + } + + keys() { + if (this.iterable.keys) { + return new Functified(this.iterable.keys()); + } else { + throw "doesn't have keys"; + } + } + + values() { + if (this.iterable.values) { + return new Functified(this.iterable.values()); + } else { + throw "doesn't have values"; + } + } + + toArray() { + var result = []; + for (let value of this.iterable) { + result.push(value); + } + return result; + } + + toObject() { + var result = {}; + for (let value of this.iterable) { + if (Array.isArray(value)) { + result[value[0]] = value[1]; + } + } + return result; + } + + toPausable() { + var iterator = this.iterable[Symbol.iterator](); + var functified = Functified.fromGenerator(function* () { + while (true) { + var result = iterator.next(); + if (result.done) { + break; + } else { + yield result.value; + } + } + }); + functified.isPausable = true; + return functified; + } + + toString() { + var i = 0; + var result = "["; + result += this.reduce((str, n) => str + (i++ > 0 ? `, ${n}` : `${n}`), ""); + result += "]"; + return result; + } + + // static methods + static fromGenerator(generator) { + return functify({ + [Symbol.iterator]: generator + }); + } + + static fromObject(obj) { + return functify({ + [Symbol.iterator]: function* () { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + yield [key, obj[key]]; + } + } + }, + entries() { + return Functified.fromGenerator(function* () { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + yield [key, obj[key]]; + } + } + }); + }, + keys() { + return Functified.fromGenerator(function* () { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + yield key; + } + } + }); + }, + values() { + return Functified.fromGenerator(function* () { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + yield obj[key]; + } + } + }); + } + }); + } + + static range(start, stop, step = 1) { + if (arguments.length === 1) { + stop = start; + start = 0; + } + return Functified.fromGenerator(function* () { + let i = start; + if (step > 0) { + while (i < stop) { + yield i; + i += step; + } + } else if (step < 0) { + while (i > stop) { + yield i; + i += step; + } + } else { + throw "step should not equal 0" + } + }); + }; + + static zip(...iterables) { + // assume if a single value is passed in it must contain an array + if (iterables.length === 1) { + iterables = iterables[0]; + } + return Functified.fromGenerator(function* () { + var iterators = iterables.map(iterable => { + if (iterable[Symbol.iterator]) { + return iterable[Symbol.iterator](); + } else { + throw "can't zip a non-iterable"; + } + }); + while (true) { + let vector = []; + for (let iterator of iterators) { + var result = iterator.next(); + if (result.done) { + return; // finished + } else { + vector.push(result.value); + } + } + yield vector; + } + }); + } +} + +function functify(iterable) { + if (!iterable[Symbol.iterator]) { + return Functified.fromObject(iterable); + } else { + return new Functified(iterable); + } +} + +functify.fromGenerator = Functified.fromGenerator; +functify.range = Functified.range; +functify.zip = Functified.zip; + +export default functify; From d99855505c891569c7600bee38a2d6b3c5ffa280 Mon Sep 17 00:00:00 2001 From: Barney Carroll Date: Mon, 20 Feb 2017 17:47:31 +0000 Subject: [PATCH 3/4] Pckg points Node, Rollup, Webpack to correct main --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 57db471..8579e5d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,9 @@ "name": "functify", "version": "0.4.0", "description": "Add functional methods like map, reduce, filter, etc. iterables (ES6 Iterators).", - "main": "dist/functify.js", + "main": "src/functify", + "module": "src/functify.mjs", + "jsnext:main": "src/functify.mjs", "directories": { "test": "test" }, From 46c5a3217b1c361b791bafaff6f05340c3d4cc69 Mon Sep 17 00:00:00 2001 From: Barney Carroll Date: Mon, 20 Feb 2017 18:04:37 +0000 Subject: [PATCH 4/4] Clean up README Remove broken examples, syntax-highlight code samples, correct style (`of` not `in`, `const` and `let` as appropriate) --- README.md | 196 +++++++++++++++++++++++++++++------------------------- 1 file changed, 106 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 34e0e11..2fe7f6f 100644 --- a/README.md +++ b/README.md @@ -20,25 +20,23 @@ methods can be called in-line as opposed composing function _a priori_. ## Usage - let f = require('functify'); +```javascript +const f = require('functify'); - let numbers = f([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - - for (let odd in numbers.filter(n => n % 2)) { - console.log(n); // 1, 3, 5, ... - } - - for (let even in numbers.filter(n => !(n % 2))) { - console.log(n); // 2, 4, 6, ... - } - - for (let [odd, even] in numbers.split(n => n % 2, n => !(n % 2)).zip()) { - console.log(`odd = ${odd}, even = ${even}`); // [1, 2], [3, 4], ... - } - - for (let square in numbers.take(3).map(n => n * n)) { - console.log(square); // 1, 4, 9 - } +const numbers = f([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + +for (const odd of numbers.filter(n => n % 2)) { + console.log(n); // 1, 3, 5, ... +} + +for (const even of numbers.filter(n => !(n % 2))) { + console.log(n); // 2, 4, 6, ... +} + +for (const square of numbers.take(3).map(n => n * n)) { + console.log(square); // 1, 4, 9 +} +``` ## Maps and Objects @@ -50,21 +48,27 @@ The new Map class in ES6 has three methods which return iterators: A Map instance itself can be used as an iterator, e.g. - let map = new Map(); - map.set('x', 5); - map.set('y', 10); - - for (let [k, v] of map) { - console.log(`map['${k}'] = ${v}`); // map['x'] = 5, map['y'] = 10 - } +```javascript +const map = new Map([ + ['x', 5], + ['y', 10] +]); + +for (const [k, v] of map) { + console.log(`map['${k}'] = ${v}`); // map['x'] = 5, map['y'] = 10 +} +``` `functify` wraps Map instances and exposes versions of `keys()`, `values()`, and `entries()` that methods like `map()` and `filter()` can be chained to, e.g. - for (let v2 of functify(map).entries().map(pair => pair[1] * pair[1])) { - console.log(v2); // 25, 100 - } - + +```javascript +for (const v2 of functify(map).entries().map(pair => pair[1] * pair[1])) { + console.log(v2); // 25, 100 +} +``` + Note: chaining in the opposite order is not allowed because map may return something that isn't an entry, i.e. a [key, value] pair. @@ -76,15 +80,17 @@ arrays which consume memory. `functify` wraps Object instances, adding `keys()`, `values()`, and `entries()` methods along with all the other methods that `functify` provides. - let obj = { - x: 5, - y: 10 - } - - for (let [k, v] of functify(obj)) { - console.log(`obj['${k}'] = ${v}`); // obj['x'] = 5, obj['y'] = 10 - } - +```javascript +const obj = { + x: 5, + y: 10 +} + +for (const [k, v] of functify(obj)) { + console.log(`obj['${k}'] = ${v}`); // obj['x'] = 5, obj['y'] = 10 +} +``` + The combines the simple creation and access syntax of Objects with the powerful iterators provided by Map. @@ -93,38 +99,44 @@ iterators provided by Map. functify wraps iterables in an object with methods to performan map, reduce, filter, etc. This object is also iterable. Here's how: - class Functified { - constructor(iterable) { - this.iterable = iterable; - } +```javascript +class Functified { + constructor(iterable) { + this.iterable = iterable; + } - *[Symbol.iterator]() { - for (let value of this.iterable) { - yield value; - } + *[Symbol.iterator]() { + for (let value of this.iterable) { + yield value; } + } - // various instance and static methods + // various instance and static methods +``` In order to make it easier to write methods on Functified there's also a static method `fromGenerator(generator)` which takes a generator and returns an iterator. - static fromGenerator(generator) { - return funcitify({ - [Symbol.iterator]: generator - }); - } - +```javascript +static fromGenerator(generator) { + return funcitify({ + [Symbol.iterator]: generator + }); +} +``` + This allows methods to be easily implemented. Here's the implementation for `map`: - map(callback) { - var iterable = this.iterable; - return Functified.fromGenerator(function* () { - for (let value of iterable) { - yield callback(value); - } - }); - } +```javascript +map(callback) { + var iterable = this.iterable; + return Functified.fromGenerator(function* () { + for (let value of iterable) { + yield callback(value); + } + }); +} +``` ## Pausable @@ -133,36 +145,40 @@ with those values, and then resume taking values where you left of at some point in the future. Normally you would have to resort to creating an iterator and calling `next()` manually, e.g. - var numbers = [1,2,3,4,5]; - var iterator = numbers[Symbol.iterator](); - - for (let i = 0; i < 2; i++) { - console.log(iterator.next().value); - } - - // do something else - - while (true) { - let result = iterator.next(); - if (result.done) { - break; - } - let value = iterator.next().value; - let square = value * value; - console.log(value * value); +```javascript +const numbers = [1,2,3,4,5]; +const iterator = numbers[Symbol.iterator](); + +for (let i = 0; i < 2; i++) { + console.log(iterator.next().value); +} + +// do something else + +while (true) { + const result = iterator.next(); + if (result.done) { + break; } + const value = iterator.next().value; + const square = value * value; + console.log(square); +} +``` The `toPausable()` creates an iterator Below is an example of how this works. - var numbers = [1,2,3,4,5]; - var pausableNumbers = numbers.toPausable(); - - for (let n of pausableNumbers.take(2)) { - console.log(n); // 1 2 - } - - // do something else - - for (let n of pausableNumbers.map(x => x * x).takeUntil(x => x > 16)) { - console.log(n); // 9 16 - } +```javascript +const numbers = [1,2,3,4,5]; +const pausableNumbers = numbers.toPausable(); + +for (const n of pausableNumbers.take(2)) { + console.log(n); // 1 2 +} + +// do something else + +for (const n of pausableNumbers.map(x => x * x).takeUntil(x => x > 16)) { + console.log(n); // 9 16 +} +```