From bbd4341b4397fd1675b53dce543447a7b1e0d182 Mon Sep 17 00:00:00 2001 From: Joel Thoms Date: Tue, 25 Sep 2018 11:48:57 -0700 Subject: [PATCH 1/2] add iterable support to filter, map, reduce --- internal/iterableSerialReduce.js | 16 ++++++++++++++++ internal/serialReduce.js | 13 ------------- list/__tests__/filter.test.js | 22 ++++++++++++++++++++-- list/__tests__/map.test.js | 23 +++++++++++++++++++++-- list/__tests__/reduce.test.js | 22 ++++++++++++++++++++-- list/filter.js | 19 ++++++++++++------- list/map.js | 20 +++++++++++++------- list/reduce.js | 16 ++++++++++------ 8 files changed, 112 insertions(+), 39 deletions(-) create mode 100644 internal/iterableSerialReduce.js delete mode 100644 internal/serialReduce.js diff --git a/internal/iterableSerialReduce.js b/internal/iterableSerialReduce.js new file mode 100644 index 0000000..a1e3505 --- /dev/null +++ b/internal/iterableSerialReduce.js @@ -0,0 +1,16 @@ +/* eslint-disable */ +const iterableSerialReduce = (func, initial, iterable, promise = Promise.resolve(initial)) => { + const iterator = iterable[Symbol.iterator]() + const { value, done } = iterator.next() + return done + ? promise + : promise.then(() => + iterableSerialReduce( + func, + initial, + iterator, + promise.then(acc => func(acc, value)) + )) +} + +module.exports = iterableSerialReduce diff --git a/internal/serialReduce.js b/internal/serialReduce.js deleted file mode 100644 index a876ec9..0000000 --- a/internal/serialReduce.js +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint-disable */ -const serialReduce = (func, init, xs, i = 0, promise = Promise.resolve(init)) => - i >= xs.length - ? promise - : serialReduce( - func, - init, - xs, - i + 1, - promise.then(acc => func(acc, xs[i])) - ) - -module.exports = serialReduce diff --git a/list/__tests__/filter.test.js b/list/__tests__/filter.test.js index 3be3a34..c137efb 100644 --- a/list/__tests__/filter.test.js +++ b/list/__tests__/filter.test.js @@ -3,17 +3,35 @@ const filter = require("../filter") describe("list/filter", () => { const isOdd = num => num % 2 !== 0 const asyncIsOdd = num => Promise.resolve(num).then(isOdd) + function* iterator() { + yield 1 + yield 2 + yield 3 + } - test("sync filters", () => { + test("sync array", () => { const expected = [1, 3] const actual = filter(isOdd)([1, 2, 3]) expect(actual).toMatchObject(expected) }) - test("async filters", () => { + test("async array", () => { expect.assertions(1) const expected = [1, 3] const actual = filter(asyncIsOdd)([1, 2, 3]) return expect(actual).resolves.toMatchObject(expected) }) + + test("sync iterator", () => { + const expected = [1, 3] + const actual = filter(isOdd)(iterator()) + expect(actual).toMatchObject(expected) + }) + + test("async iterator", () => { + expect.assertions(1) + const expected = [1, 3] + const actual = filter(asyncIsOdd)(iterator()) + return expect(actual).resolves.toMatchObject(expected) + }) }) diff --git a/list/__tests__/map.test.js b/list/__tests__/map.test.js index bf8b5c3..f5f5ee1 100644 --- a/list/__tests__/map.test.js +++ b/list/__tests__/map.test.js @@ -3,18 +3,37 @@ const map = require("../map") describe("list/map", () => { const double = num => num * 2 const asyncDouble = num => Promise.resolve(num).then(double) + function* iterator() { + yield 1 + yield 2 + yield 3 + } - test("sync map", () => { + test("sync array", () => { expect.assertions(1) const expected = [2, 4, 6] const actual = map(double)([1, 2, 3]) expect(actual).toMatchObject(expected) }) - test("async maps", () => { + test("async array", () => { expect.assertions(1) const expected = [2, 4, 6] const actual = map(asyncDouble)([1, 2, 3]) return expect(actual).resolves.toMatchObject(expected) }) + + test("sync iterable", () => { + expect.assertions(1) + const expected = [2, 4, 6] + const actual = map(double)(iterator()) + expect(actual).toMatchObject(expected) + }) + + test("async iterable", () => { + expect.assertions(1) + const expected = [2, 4, 6] + const actual = map(asyncDouble)(iterator()) + return expect(actual).resolves.toMatchObject(expected) + }) }) diff --git a/list/__tests__/reduce.test.js b/list/__tests__/reduce.test.js index edaf946..c912f33 100644 --- a/list/__tests__/reduce.test.js +++ b/list/__tests__/reduce.test.js @@ -3,17 +3,35 @@ const reduce = require("../reduce") describe("list/reduce", () => { const add = x => y => x + y const asyncAdd = x => y => Promise.resolve(x).then(add(y)) + function* iterator() { + yield 1 + yield 2 + yield 3 + } - test("sync reduces", () => { + test("sync array", () => { const expected = 6 const actual = reduce(add)(0)([1, 2, 3]) expect(actual).toBe(expected) }) - test("async reduces", () => { + test("async array", () => { expect.assertions(1) const expected = 6 const actual = reduce(asyncAdd)(0)([1, 2, 3]) return expect(actual).resolves.toBe(expected) }) + + test("sync iterable", () => { + const expected = 6 + const actual = reduce(add)(0)(iterator()) + expect(actual).toBe(expected) + }) + + test("async iterable", () => { + expect.assertions(1) + const expected = 6 + const actual = reduce(asyncAdd)(0)(iterator()) + return expect(actual).resolves.toBe(expected) + }) }) diff --git a/list/filter.js b/list/filter.js index 6322ff2..4f3e2ac 100644 --- a/list/filter.js +++ b/list/filter.js @@ -1,6 +1,6 @@ /* eslint-disable */ const isThenable = require('../internal/isThenable') -const serialReduce = require('../internal/serialReduce') +const iterableSerialReduce = require('../internal/iterableSerialReduce') const asyncFilterReducer = func => (acc, x) => { const result = func(x) @@ -11,15 +11,20 @@ const asyncFilterReducer = func => (acc, x) => { const filter = func => iterable => { const values = [] - for (let i = 0; i < iterable.length; i++) { - const x = iterable[i] - const result = func(x) + const iterator = iterable[Symbol.iterator]() + var { value, done } = iterator.next() + + while (!done) { + const result = func(value) if (isThenable(result)) { - return serialReduce(asyncFilterReducer(func), [], values.concat(iterable), i + 1, result.then(() => [ x ])) - } else if (result) { - values.push(x) + return iterableSerialReduce(asyncFilterReducer(func), null, iterator, result.then(() => [ value ])) + } + if (result) { + values.push(value) } + var { value, done } = iterator.next() } + return values } diff --git a/list/map.js b/list/map.js index 370c37a..d4c6a3d 100644 --- a/list/map.js +++ b/list/map.js @@ -1,6 +1,6 @@ /* eslint-disable */ const isThenable = require('../internal/isThenable') -const serialReduce = require('../internal/serialReduce') +const iterableSerialReduce = require('../internal/iterableSerialReduce') const asyncMapReducer = func => (acc, x) => { const result = func(x) @@ -9,16 +9,22 @@ const asyncMapReducer = func => (acc, x) => { : (acc.push(result), acc) } -const map = func => xs => { +const map = func => iterable => { const values = [] - for (let i = 0; i < xs.length; i++) { - const result = func(xs[i]) + const iterator = iterable[Symbol.iterator]() + var { value, done } = iterator.next() + + while (!done) { + const result = func(value) + if (isThenable(result)) { - return serialReduce(asyncMapReducer(func), [], values.concat(xs), i + 1, result.then(v => [ v ])) - } else { - values.push(result) + return iterableSerialReduce(asyncMapReducer(func), null, iterator, result.then(v => [ v ])) } + + values.push(result) + var { value, done } = iterator.next() } + return values } diff --git a/list/reduce.js b/list/reduce.js index c93ae9a..2fa0970 100644 --- a/list/reduce.js +++ b/list/reduce.js @@ -1,16 +1,20 @@ /* eslint-disable */ const isThenable = require('../internal/isThenable') -const serialReduce = require('../internal/serialReduce') +const iterableSerialReduce = require('../internal/iterableSerialReduce') -const reduce = func => initial => list => { +const reduce = func => initial => iterable => { let acc = initial - for (let i = 0; i < list.length; i++) { - acc = func(acc)(list[i]) + const iterator = iterable[Symbol.iterator]() + var { value, done } = iterator.next() + + while (!done) { + acc = func(acc)(value) if (isThenable(acc)) { - return serialReduce((a, b) => func(a)(b), acc, list, i + 1, acc) + return iterableSerialReduce((a, b) => func(a)(b), null, iterator, acc) } + var { value, done } = iterator.next() } -// Array.prototype.reduce.call (list, (acc, val) => func (acc) (val), initial) + return acc } From dea5105481b12a74d73edc03409e767cc4db8b53 Mon Sep 17 00:00:00 2001 From: Joel Thoms Date: Tue, 25 Sep 2018 11:51:20 -0700 Subject: [PATCH 2/2] add list/range. update fizz-buzz example --- examples/fizz-buzz/main.mjs | 10 ++++------ list/__tests__/range.test.js | 9 +++++++++ list/range.js | 7 +++++++ 3 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 list/__tests__/range.test.js create mode 100644 list/range.js diff --git a/examples/fizz-buzz/main.mjs b/examples/fizz-buzz/main.mjs index 4fcabd6..20d7f57 100644 --- a/examples/fizz-buzz/main.mjs +++ b/examples/fizz-buzz/main.mjs @@ -1,8 +1,7 @@ -import after from 'mojiscript/core/after' import cond from 'mojiscript/core/cond' import pipe from 'mojiscript/core/pipe' -import pipeR from 'mojiscript/core/pipeR' -import when from 'mojiscript/core/when' +import map from 'mojiscript/list/map' +import range from 'mojiscript/list/range' import S from 'sanctuary' // getFizzInfo :: Number -> FizzInfo @@ -33,9 +32,8 @@ const fizzBuzz = log => pipe ([ log ]) -const main = ({ last, log }) => pipeR (next => [ - when (n => n <= last) - (after (fizzBuzz (log)) (x => next (x + 1))) +const main = ({ last, log }) => pipe ([ + map (fizzBuzz (log)) (range (1) (last + 1)) ]) export default main diff --git a/list/__tests__/range.test.js b/list/__tests__/range.test.js new file mode 100644 index 0000000..a7e4b4c --- /dev/null +++ b/list/__tests__/range.test.js @@ -0,0 +1,9 @@ +const range = require('../range') + +describe('list/range', () => { + test('creates range', () => { + const expected = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + const actual = [...range(0)(10)] + expect(actual).toMatchObject(expected) + }) +}) diff --git a/list/range.js b/list/range.js new file mode 100644 index 0000000..e48f348 --- /dev/null +++ b/list/range.js @@ -0,0 +1,7 @@ +/* eslint-disable */ +const range = (start) => function* (end) { + let current = start + while (current < end) yield current++ +} + +module.exports = range