Skip to content

Commit b8ae547

Browse files
committed
feat: 🎸 add mapSpread operator
to allow tranformation output tuples to tuples
1 parent 65a9a1f commit b8ae547

5 files changed

Lines changed: 80 additions & 15 deletions

File tree

index.js

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,18 +160,19 @@ const chain = (...fns) => cpsFn => {
160160
* If `fj` is undefined or null, the output is passed unchanged.
161161
*
162162
* @example
163-
* const cpsFn = (cb1, cb2) => {cb1(2, 3); cb2(7)}
164-
* // 2 callbacks receive corresponding outputs (2, 3) and (7)
165-
* map(f1, f2)(cpsFn)
163+
* // 2 callbacks receive respective outputs (2,3) and (7)
164+
* const cpsFn = (cb0,cb1) => {cb0(2,3); cb1(7)}
165+
* const f0 = (x,y) => x+y
166+
* const f1 = z => z*6
167+
* map(f0,f1)(cpsFn)
166168
* // is equivalent to the CPS function
167-
* (cb1, cb2) => {cb1(f1(2, 3)); cb2(f2(7))}
168-
* // where f1 and f2 transform respective outputs.
169+
* (cb0,cb1) => {cb0(2+3); cb1(f1(7*6))}
169170
*
170171
* @example
171-
* const cpsFromPromise = promise => (onRes, onErr) => promise.then(onRes, onErr)
172-
* map(f1, f2)(cpsFromPromise(promise))
172+
* const cpsFromPromise = promise => (onRes,onErr) => promise.then(onRes,onErr)
173+
* map(f0,f1)(cpsFromPromise(promise))
173174
* // is equivalent to
174-
* cpsFromPromise(promise.then(f1).catch(f2))
175+
* cpsFromPromise(promise.then(f0).catch(f1))
175176
*/
176177
// precompose every callback with fn from array matched by index
177178
// if no function provided, default to the identity
@@ -180,6 +181,25 @@ const map = (...fns) => chain(...fns.map((f, idx) =>
180181
))
181182

182183

184+
/**
185+
* Same as `map` but spread return values of transforming functions.
186+
* This allows to transform output tuples into tuples rather than single values as `map` does.
187+
* As JavaScript has no tuples, use arrays instead.
188+
*
189+
* @example
190+
* // 1 callback receives output `(2,3)`
191+
* const cpsFn = cb => cb(2,3)
192+
* // `f` transforms `(x,y)` to `(x+y,x-y)` (written as array)
193+
* const f = (x,y) => [x+y,x-y]
194+
* map(f)(cpsFn)
195+
* // is equivalent to the CPS function
196+
* cb => cb(2+3,2-3)
197+
*/
198+
exports.mapSpread = (...fns) => chain(...fns.map((f, idx) =>
199+
(...args) => ofN(idx)(...f(...args))
200+
))
201+
202+
183203

184204
/**
185205
* Filter outputs making predicates `(pred0,...,predn)` truthy.

test/filter.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ test('filter tuples', t => {
2424
const cpsFun = cb => cb(1,2) + cb(3,4)
2525
t.plan(1)
2626
const cpsNew = filter(a => a > 1)(cpsFun)
27-
cpsNew(t.cis(3))
27+
cpsNew(t.cis(3,4))
2828
})
2929

3030
test('filter multiple outputs', t => {

test/helpers/ava-patched.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Usage: t.cis(a)(b) instead of t.is(a, b)
88
*/
99
module.exports = (title, fn) =>
1010
test(title, t => {
11-
t.cis = a => b => t.is(a, b)
12-
t.cDeepEqual = a => b => t.deepEqual(a, b)
11+
t.cis = (...a) => (...b) => t.deepEqual(a, b)
12+
t.cDeepEqual = (...a) => (...b) => t.deepEqual(a, b)
1313
return fn(t)
1414
})

test/map.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ test('map over single function with no arguments', t => {
1616
map(() => 30)(cpsFun)(t.cis(30))
1717
})
1818

19-
test('further callbacks are unaffected when map over single function', t => {
19+
test('mapSpread preserves outputs for missing transforms', t => {
2020
const cpsFun = (cb1, cb2) => {cb1(42); cb2(23)}
2121
map(x => x*2)(cpsFun)(x => x, t.cis(23))
2222
})
@@ -32,8 +32,8 @@ test('map over more functions than callbacks, the extra functions are ignored',
3232
map(x => x*2, x => x+10)(cpsFun)(t.cis(84))
3333
})
3434

35-
test('map over nested arrays', t => {
36-
const cf0 = cb => cb([1,2])
37-
map(arr => arr.map(a => a + 1))(cf0)(t.cDeepEqual([2,3]))
35+
test('map over arrays', t => {
36+
const F = cb => cb([1,2])
37+
map(arr => arr.map(a => a + 1))(F)(t.cDeepEqual([2,3]))
3838
})
3939

test/mapSpread.test.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const test = require('./config')
2+
const { mapSpread } = require('..')
3+
4+
test('mapSpread transforms output 1-tuples to m-tuples', t => {
5+
const cpsFun = cb => cb(42)
6+
mapSpread(x => [])(cpsFun)(t.cis())
7+
mapSpread(x => [x*2])(cpsFun)(t.cis(84))
8+
mapSpread(x => [x,x*2])(cpsFun)(t.cis(42,84))
9+
mapSpread(x => [x,x*2,5])(cpsFun)(t.cis(42,84,5))
10+
})
11+
12+
test('mapSpread transforms output 2-tuples to m-tuples', t => {
13+
const cpsFun = cb => cb(42, 24)
14+
mapSpread((a,b)=>[])(cpsFun)(t.cis())
15+
mapSpread((a,b)=>[a-b])(cpsFun)(t.cis(18))
16+
mapSpread((a,b)=>[a,a-b,b,1])(cpsFun)(t.cis(42,18,24,1))
17+
})
18+
19+
test('mapSpread over single function with no arguments', t => {
20+
const cpsFun = cb => cb()
21+
mapSpread(() => [])(cpsFun)(t.cis())
22+
mapSpread(() => [30])(cpsFun)(t.cis(30))
23+
})
24+
25+
test('mapSpread preserves outputs for missing transforms', t => {
26+
const cpsFun = (cb1, cb2) => {cb1(42); cb2(23)}
27+
mapSpread(x => [x*2,1,4])(cpsFun)(_ => null, t.cis(23))
28+
})
29+
30+
test('mapSpread over multiple functions', t => {
31+
t.plan(4)
32+
const cpsFun = (cb1, cb2) => {cb1(42); cb2(23)}
33+
mapSpread(x => [x/2], x => [x*2])(cpsFun)(t.cis(21), t.cis(46))
34+
mapSpread(x => [x/2,x], x => [x*2,-x])(cpsFun)(t.cis(21,42), t.cis(46,-23))
35+
})
36+
37+
test('mapSpread over more functions than callbacks, the extra functions are ignored', t => {
38+
const cpsFun = cb => cb(42)
39+
mapSpread(x => [x*2], x => [x+10])(cpsFun)(t.cis(84))
40+
})
41+
42+
test('mapSpread transforms tuples via arrays', t => {
43+
const F = cb => cb(1,2)
44+
mapSpread((...arr) => arr.map(a => a + 1))(F)(t.cDeepEqual(2,3))
45+
})

0 commit comments

Comments
 (0)