Skip to content

Commit

Permalink
BREAKING: simplify scan signature
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitriz committed Jun 9, 2019
1 parent 65732c4 commit ba9885f
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 25 deletions.
27 changes: 16 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,23 +242,26 @@ 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`.
The new accumulated value is passed as output into the first callback.


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

Expand Down
12 changes: 7 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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))
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 ba9885f

Please sign in to comment.