Skip to content

Commit

Permalink
Merge pull request #27 from dmitriz/scan-to-one-value
Browse files Browse the repository at this point in the history
Scan simplified to one value only
  • Loading branch information
dmitriz authored Jun 9, 2019
2 parents 65732c4 + 2566da7 commit c0b0e62
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 33 deletions.
26 changes: 15 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,23 +242,25 @@ 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, ...)
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`.


#### Example of `scan`
Expand All @@ -273,17 +275,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),
)
```

Expand Down
32 changes: 19 additions & 13 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,25 +176,29 @@ 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`
* @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 = (...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 => (...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))
Expand All @@ -217,7 +221,9 @@ const objMap = fn => obj =>
// Prototype methods
const protoObj = objMap(apply2this)({
map,
chain
chain,
filter,
scan
})

const CPS = cpsFn => {
Expand Down
18 changes: 9 additions & 9 deletions test/scan.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
})
})

0 comments on commit c0b0e62

Please sign in to comment.