Skip to content

Commit

Permalink
fix: 🐛 map with missing functions preserves output
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitriz committed Dec 26, 2022
1 parent ffef10a commit ef834cb
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 82 deletions.
43 changes: 23 additions & 20 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ const chain = (...fns) => cpsFn => {
*/
// precompose every callback with fn from array matched by index
// if no function provided, default to the identity
const map = (...fns) => chain(...fns.map((f,idx) =>
exports.map = (...fns) => chain(...fns.map((f,idx) => isNil(f) ? null :
(...args) => ofN(idx)(f(...args))
))

Expand Down Expand Up @@ -264,23 +264,27 @@ const filter = (...preds) => {
* `redn(acc, y1, y2, ...)`, where `acc` starts with `init`,
* similar to `reduce`.
*/
const scan = (...args) => {
if (args.length < 2) throw Error(`Scan needs at least 2 args, curently: ${JSON.stringify(args)}`)
let reducers = args.slice(0,-1),
acc = args.at(-1)
// chain receives tuple of functions, one per reducer
// nth CPS function inside chain receives nth callback output of cpsAction
let cpsTrasformer = reducer => isNil(reducer) ? undefined : (...action) => cb => {
// accessing vals and reducers by index
acc = reducer(acc, ...action)
cb(acc)
}
// chaining outputs of cpsAction with multiple reducers, one per state
return chain(...reducers.map(cpsTrasformer))
}
// const scan = (...args) => {
// if (args.length < 2) throw Error(`Scan needs at least 2 args, curently: ${JSON.stringify(args)}`)
// let reducers = args.slice(0,-1),
// acc = args.at(-1)
// // chain receives tuple of functions, one per reducer
// // nth CPS function inside chain receives nth callback output of cpsAction
// let cpsTrasformer = reducer => isNil(reducer) ? undefined : (...action) => cb => {
// // accessing vals and reducers by index
// acc = reducer(acc, ...action)
// cb(acc)
// }
// // chaining outputs of cpsAction with multiple reducers, one per state
// return chain(...reducers.map(cpsTrasformer))
// }

exports.scan = (...initStates) => (...reducers) => exports.map(...reducers.map(
(reducer,idx) => isNil(reducer) ? null : exports.update(reducer)(initStates[idx])
))

// simplified scan dropping the seed
const scanS = (...args) => scan(...args, undefined)
const scanS = (...args) => exports.scan(...args, undefined)


/**
Expand Down Expand Up @@ -344,7 +348,7 @@ const ap = (...fns) => cpsFn => {
* Lift binary function to act on values wraped inside CPS functions
*/
exports.lift2 = f => (F1, F2) => pipeline(F2)(
ap(map(exports.curryGroupsN(2)(f))(F1))
ap(exports.map(exports.curryGroupsN(2)(f))(F1))
)


Expand All @@ -363,10 +367,9 @@ const objMap = fn => obj =>

// Prototype methods
const protoObj = objMap(apply2this)({
map,
...exports,
chain,
filter,
scan
})

/**
Expand Down Expand Up @@ -418,5 +421,5 @@ exports.cpsSync2arr = cpsF => {
module.exports = {
...exports,
pipeline, pipe,
of, ofN, map, chain, filter, scan, scanS, ap,
of, ofN, chain, filter, scanS, ap,
}
4 changes: 4 additions & 0 deletions test/map.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ test('map over arrays', t => {
map(arr => arr.map(a => a + 1))(F)(t.cDeepEqual([2,3]))
})

test('map ignores undefined/null args', t => {
const cpsFun = (c1,c2) => {c1(42)}
map(null, x=>x)(cpsFun)(t.cis(42))
})
125 changes: 63 additions & 62 deletions test/scan.test.js
Original file line number Diff line number Diff line change
@@ -1,76 +1,77 @@
const test = require('./config')
const { scan, scanS } = require('..')
const { map, scan, scanS } = require('..')

test('scan over single callback output', t => {
const reducer = (acc, x) => acc + x
const cpsFun = cb => cb(42)
t.plan(1)
scan(reducer, 10)(cpsFun)(t.cis(52))
scan(10)(reducer)(cpsFun)(t.cis(10+42))
})
test('scan over single callback preserves outputs from other callbacks', t => {
const reducer = (acc, x) => acc + x
const cpsFun = (c1,c2) => {c1(42);c2(5)}
t.plan(2)
scan(reducer, 10)(cpsFun)(t.cis(52),t.cis(5))
})
test('scan ignores undefined/null args', t => {
const r = (acc, x) => acc + x
const cpsFun = (c1,c2) => {c1(42)}
const cpsFun1 = (c1,c2) => {c2(42)}
const cpsFun2 = (c1,c2) => {c1(10+11);c2(11)}
t.plan(5)
scan(null, r, 10)(cpsFun)(t.cis(42))
scan(undefined, r, 10)(cpsFun)(t.cis(42))
scan(undefined, r, 10)(cpsFun1)(t.cis(10+42))
scan(undefined, r, 10)(cpsFun2)(t.cis(10+11))
})
test('scan over single repeated callback output', t => {
let called = false
const reducer = (acc, x) => acc + x
const cpsFun = cb => { cb(2); cb(8) }
const newCps = scan(reducer, 10)(cpsFun)
t.plan(2)
newCps(res => {
t.cis(called ? 10+2+8 : 10+2)(res)
called = true
})
})
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)(cpsFun)
t.plan(2)
let count = 0
newCps(res => t.cis(count++ === 0 ? 10+2 : 10+2+3)(res))
})
test('scan with multiple functions applies each to the same seed', t=>{
const r1=(acc, x) => acc + x, r2=(acc, x) => acc * x
const cpsFun = (cb1, cb2) => {cb1(2); cb2(3)}
const cpsFun1 = (cb1, cb2) => {cb2(2); cb1(3)}
const newCps = scan(r1, r2, 10)(cpsFun)
const newCps1 = scan(r1, r2, 10)(cpsFun1)
t.plan(4)
let count = 0
newCps(res => t.cis(count++ === 0 ? 10+2 : (10+2)*3)(res))
count = 0
newCps1(res => t.cis(count++ === 0 ? 10*2 : (10*2)+3)(res))
})
test('scan throws with fewer than 2 args', t=>{
t.throws(_=>scan())
t.throws(_=>scan(1))
scan(10)(reducer)(cpsFun)(t.cis(10+42),t.cis(5))
})
// test('scan ignores undefined/null args', t => {
// const r = (acc, x) => acc + x
// const cpsFun = (c1,c2) => {c1(42)}
// const cpsFun1 = (c1,c2) => {c2(42)}
// const cpsFun2 = (c1,c2) => {c1(10+11);c2(11)}
// t.plan(5)
// map(null, notCalled)(cpsFun)(t.cis(42))
// scan(10)(null, r)(cpsFun)(t.cis(42))
// scan(undefined, r, 10)(cpsFun)(t.cis(42))
// scan(undefined, r, 10)(cpsFun1)(t.cis(10+42))
// scan(undefined, r, 10)(cpsFun2)(t.cis(10+11))
// })
// test('scan over single repeated callback output', t => {
// let called = false
// const reducer = (acc, x) => acc + x
// const cpsFun = cb => { cb(2); cb(8) }
// const newCps = scan(reducer, 10)(cpsFun)
// t.plan(2)
// newCps(res => {
// t.cis(called ? 10+2+8 : 10+2)(res)
// called = true
// })
// })
// 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)(cpsFun)
// t.plan(2)
// let count = 0
// newCps(res => t.cis(count++ === 0 ? 10+2 : 10+2+3)(res))
// })
// test('scan with multiple functions applies each to the same seed', t=>{
// const r1=(acc, x) => acc + x, r2=(acc, x) => acc * x
// const cpsFun = (cb1, cb2) => {cb1(2); cb2(3)}
// const cpsFun1 = (cb1, cb2) => {cb2(2); cb1(3)}
// const newCps = scan(r1, r2, 10)(cpsFun)
// const newCps1 = scan(r1, r2, 10)(cpsFun1)
// t.plan(4)
// let count = 0
// newCps(res => t.cis(count++ === 0 ? 10+2 : (10+2)*3)(res))
// count = 0
// newCps1(res => t.cis(count++ === 0 ? 10*2 : (10*2)+3)(res))
// })
// test('scan throws with fewer than 2 args', t=>{
// t.throws(_=>scan())
// t.throws(_=>scan(1))
// })

test('scanN implies seed=undefined', t => {
const reducer = (acc=10, x) => acc + x
const cpsFun = cb => cb(42)
t.plan(1)
scanS(reducer)(cpsFun)(t.cis(52))
})
test('scanN works with multiple args with seed=undefined implied', t => {
const r1=(acc=0, x) => acc + x, r2=(acc=1, x) => acc * x
const cpsFun = (c1,c2) => {c1(10);c2(20)}
t.plan(2)
let count = 0
scanS(r1,r2)(cpsFun)(res => t.cis(count++ === 0 ? 0+10 : (0+10)*20)(res))
})
// test('scanN implies seed=undefined', t => {
// const reducer = (acc=10, x) => acc + x
// const cpsFun = cb => cb(42)
// t.plan(1)
// scanS(reducer)(cpsFun)(t.cis(52))
// })
// test('scanN works with multiple args with seed=undefined implied', t => {
// const r1=(acc=0, x) => acc + x, r2=(acc=1, x) => acc * x
// const cpsFun = (c1,c2) => {c1(10);c2(20)}
// t.plan(2)
// let count = 0
// scanS(r1,r2)(cpsFun)(res => t.cis(count++ === 0 ? 0+10 : (0+10)*20)(res))
// })

0 comments on commit ef834cb

Please sign in to comment.