From ba9885f4f5c6a65555d73c0b42fea67d9b8d80a6 Mon Sep 17 00:00:00 2001 From: dmitriz Date: Sun, 9 Jun 2019 19:38:36 +0700 Subject: [PATCH 1/3] BREAKING: simplify scan signature --- README.md | 27 ++++++++++++++++----------- index.js | 12 +++++++----- test/scan.test.js | 18 +++++++++--------- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 2fc8038..94ddd88 100644 --- a/README.md +++ b/README.md @@ -242,15 +242,15 @@ const copyNotEmpty = CPS(readFileCps('source.txt')) copyNotEmpty(err => console.error(err)) ``` -### `scan(...reducers)(...initialValues)(cpsFunction)` +### `scan(...reducers, init)(cpsFunction)` Similar to [`reduce`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce), except that all partial accumulated values are passed into callback whenever there is new output. ```js // these are equivalent -scan(red1, red2, ...)(x1, x2, ...)(cpsFn) -(cpsFn).scan(red1, red2, ...)(x1, x2, ...) -pipeline(cpsFn)(scan(red1, red2, ...)(x1, x2, ...)) +scan(red1, red2, ..., init)(cpsFn) +(cpsFn).scan(red1, red2, ..., init) +pipeline(cpsFn)(scan(red1, red2, ..., init)) ``` -where each `redn` is a *reducer* +where each `redn` is a *reducer* and `init` is the initial accumulated value. ```js // compute new accumulator value from the old one // and the tuple of current values (y1, y2, ...) @@ -258,7 +258,10 @@ const redn = (acc, y1, y2, ...) => ... ``` #### Result of applying `scan` -New CPS function whose output from the `n`the callback is the `n`th accumulated value `accn`. Upon each output `(y1, y2, ...)`, the new acculated value `redn(accn, y1, y2, ...)` is computed and passed into the callback. The nth value `xn` serves in place of `acc` at the start, similar to `reduce`. Note that the initial values `(x1, x2, ...)` must be passed as curried arguments to avoid getting mixed with reducers. +New CPS function whose output from the first callback is the accumulated value. For each output `(y1, y2, ...)` from the `n`th callback, +the `n`th reducer `redn` is used to compute the new acculated value +`redn(acc, y1, y2, ...)`, where `acc` starts with `init`, similar to `reduce`. +The new accumulated value is passed as output into the first callback. #### Example of `scan` @@ -273,17 +276,19 @@ const getVotes = (onUpvote, onDownvote) => { ev => onDownvote(1) ) } -const add = (acc, x) => acc + x // count numbers of up- and downvotes and // pass into respective callbacks const countVotes = CPS(getVotes) - .scan(add, add)(0, 0) + .scan( + ([up, down], upvote) => [up + upvote, down], + ([up, down], downvote) => [up, down + downvote], + [0,0] + ) // countVotes is CPS function that we can call -// with any pair of callbacks +// with any callback countVotes( - upvotes => console.log(upvotes, ' votes for'), - downvotes => console.log(downvotes, ' votes against'), + votes => console.log('Total votes: ', votes), ) ``` diff --git a/index.js b/index.js index a8a682f..2fe7814 100644 --- a/index.js +++ b/index.js @@ -176,7 +176,7 @@ const filter = (...preds) => { * and outputs from CPS function regarded as actions. * `reducers` and `vals` are matched by index. * - * @signature (...reducers) -> (...vals) -> cpsAction -> cpsState + * @signature (...reducers, init) -> cpsAction -> cpsState * * @param {...Function} reducers * - functions of the form `red = (acc, ...val) => newAcc` @@ -188,13 +188,15 @@ const filter = (...preds) => { * the stream of outputs from the nth callback of `cpsFn` * over `reducers[n]` starting from with `vals[n]`. */ -const scan = (...reducers) => (...vals) => { +const scan = (...args) => { + let reducers = args.slice(0,-1), + [acc] = args.slice(-1) // chain receives tuple of functions, one per reducer // nth CPS function inside chain receives nth callback output of cpsAction - let cpsTrasformer = (reducer, idx) => (...action) => (...cbs) => { + let cpsTrasformer = (reducer, idx) => (...action) => cb => { // accessing vals and reducers by index - vals[idx] = reducer(vals[idx], ...action) - cbs[idx](vals[idx]) + acc = reducer(acc, ...action) + cb(acc) } // chaining outputs of cpsAction with multiple reducers, one per state return chain(...reducers.map(cpsTrasformer)) diff --git a/test/scan.test.js b/test/scan.test.js index 02826aa..2ed3c00 100644 --- a/test/scan.test.js +++ b/test/scan.test.js @@ -3,18 +3,16 @@ const { scan } = require('..') test('scan over single callback output', t => { const reducer = (acc, x) => acc + x - const initState = 10 const cpsFun = cb => cb(42) t.plan(1) - scan(reducer)(initState)(cpsFun)(t.cis(52)) + scan(reducer, 10)(cpsFun)(t.cis(52)) }) test('scan over single repeated callback output', t => { let called = false const reducer = (acc, x) => acc + x - const initState = 10 const cpsFun = cb => { cb(2); cb(8) } - const newCps = scan(reducer)(initState)(cpsFun) + const newCps = scan(reducer, 10)(cpsFun) // called twice with // 12 = 10 + 2 and 20 = 10 + 2 + 8 as outputs @@ -27,10 +25,12 @@ test('scan over single repeated callback output', t => { test('scan over outputs from 2 callbacks', t => { const r = (acc, x) => acc + x - const cpsFun = (cb1, cb2) => cb1(2) + cb2(3) - const newCps = scan(r, r)(10, 11)(cpsFun) - - // called with 12 = 10 + 2 and 14 = 11 + 3 + const cpsFun = (cb1, cb2) => {cb1(2); cb2(3)} + const newCps = scan(r, r, 10)(cpsFun) + // called with 12 = 10 + 2 and 15 = 12 + 3 t.plan(2) - newCps(t.cis(12), t.cis(14)) + let count = 0 + newCps(res => { + t.cis(count++ == 0 ? 12 : 15)(res) + }) }) From 036d2daf3c01ec36df2a9fb72dbc8540484d43fc Mon Sep 17 00:00:00 2001 From: dmitriz Date: Sun, 9 Jun 2019 19:44:46 +0700 Subject: [PATCH 2/3] docs(readme): update docs --- README.md | 1 - index.js | 20 ++++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 94ddd88..74865e2 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,6 @@ const redn = (acc, y1, y2, ...) => ... New CPS function whose output from the first callback is the accumulated value. For each output `(y1, y2, ...)` from the `n`th callback, the `n`th reducer `redn` is used to compute the new acculated value `redn(acc, y1, y2, ...)`, where `acc` starts with `init`, similar to `reduce`. -The new accumulated value is passed as output into the first callback. #### Example of `scan` diff --git a/index.js b/index.js index 2fe7814..cc378ce 100644 --- a/index.js +++ b/index.js @@ -179,14 +179,16 @@ const filter = (...preds) => { * @signature (...reducers, init) -> cpsAction -> cpsState * * @param {...Function} reducers - * - functions of the form `red = (acc, ...val) => newAcc` - * @param {...*} vals - tuple of arbitrary values. + * - functions of the form `red = (acc, ...vals) => newAcc` + * @param {*} init - initial value for the iteration. * @param {Function} cpsFn - CPS function. - * @returns {Function} `scan(...reducers)(...vals)(cpsFn)` - * - CPS function whose nth callback - * receives the outputs obtained by iterating - * the stream of outputs from the nth callback of `cpsFn` - * over `reducers[n]` starting from with `vals[n]`. + * @returns {Function} `scan(...reducers, init)(cpsFn)` + * - CPS function whose output from the first callback + * is the accumulated value. For each output `(y1, y2, ...)` + * from the `n`th callback of `cpsFn, the `n`th reducer `redn` + * is used to compute the new acculated value + * `redn(acc, y1, y2, ...)`, where `acc` starts with `init`, + * similar to `reduce`. */ const scan = (...args) => { let reducers = args.slice(0,-1), @@ -219,7 +221,9 @@ const objMap = fn => obj => // Prototype methods const protoObj = objMap(apply2this)({ map, - chain + chain, + filter, + scan }) const CPS = cpsFn => { From 2566da7cb2d45cb061f488e7773d27976c0b3c31 Mon Sep 17 00:00:00 2001 From: dmitriz Date: Sun, 9 Jun 2019 19:50:46 +0700 Subject: [PATCH 3/3] refactor(index): remove unused var --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index cc378ce..0e3d4d7 100644 --- a/index.js +++ b/index.js @@ -195,7 +195,7 @@ const scan = (...args) => { [acc] = args.slice(-1) // chain receives tuple of functions, one per reducer // nth CPS function inside chain receives nth callback output of cpsAction - let cpsTrasformer = (reducer, idx) => (...action) => cb => { + let cpsTrasformer = reducer => (...action) => cb => { // accessing vals and reducers by index acc = reducer(acc, ...action) cb(acc)