UNPKG

6.61 kBJavaScriptView Raw
1// Helper to inherit the prototype
2const inheritState = (target, source) => {
3 Object.setPrototypeOf(
4 target, Object.getPrototypeOf(source)
5 )
6}
7
8
9/* ----- General purpose operators ----- */
10
11/**
12 * Pipeline Operator
13 * passes tuple of values to sequence of functions
14 * similar to the UNIX pipe (x1, ..., xn) | f1 | f2 | ... | fm
15 *
16 * @name pipeline
17 * @params {Tuple} (...args) tuple of arbitrary values
18 * @curriedParams {Tuple of Functions} (...fns) tuple of functions that
19 * @returns {value} fn(...f2(f1(...args))...)
20 * where fns = [f1, f2, ..., fn]
21 *
22 * # Examples
23 *
24 * pipeline(x)(f1, f2, f3)
25 * is equivalent to f3(f2(f1(x)))
26 * pipeline(x,y)(f, g)
27 * is equivalent to g(f(x, y))
28 */
29const pipeline = (...args) => (...fns) => {
30 const f1 = fns[0]
31 return fns.slice(1).reduce(
32 (acc, fn) => fn(acc),
33 f1(...args)
34 )
35}
36
37
38/* ----- CPS operators ----- */
39
40/**
41 * Create CPS function with given tuple as immediate output
42 *
43 * @name CPS.of
44 * @params {Tuple} (...args) tuple of arbitrary values
45 * @returns {CPS Function} CPS.of(...args)
46 * that outputs (...args) inside its first callback
47 * no other output is passed to any other callback
48 *
49 * # Example
50 *
51 * CPS.of(x1, x2, x3)
52 * is equivalent to the CPS function
53 * cb => cb(x1, x2, x3)
54 *
55 * The pair (CPS.map, CPS.of) conforms to the Pointed Functor spec,
56 * see {@link https://stackoverflow.com/a/41816326/1614973}
57 */
58const of = (...args) => cb => cb(...args)
59
60
61/**
62 * Map CPS function over arbitrary tuple of functions, where for each n,
63 * the nth function from the tuple transforms the output of the nth callback
64 *
65 * @signature (...fns) -> CPS -> CPS (curried)
66 *
67 * @name CPS.map
68 * @params {Tuple of Functions} (...fns)
69 * @curriedParam {CPS Function} cpsFun
70 * @returns {CPS Function} CPS.map(...fns)(cpsFun)
71 * whose nth callback's output equals
72 * the nth callback's output of `cpsFun` transformed via function fns[n]
73 *
74 * # Examples
75 *
76 * const cpsFun = (cb1, cb2) => cb1(2, 3) + cb2(7)
77 * 2 callbacks receive corresponding outputs (2, 3) and (7)
78 * CPS.map(f1, f2)(cpsFun)
79 * is equivalent to the CPS function
80 * (cb1, cb2) => cb1(f1(2, 3)) + cb2(f2(7))
81 * where f1 and f2 transform respective outputs
82 *
83 * const cpsFromPromise = promise => (onRes, onErr) => promise.then(onRes, onErr)
84 * CPS.map(f1, f2)(cpsFromPromise(promise))
85 * is equivalent to
86 * cpsFromPromise(promise.then(f1).catch(f2))
87 *
88 * The pair (CPS.map, CPS.of) conforms to the Pointed Functor spec,
89 * see {@link https://stackoverflow.com/a/41816326/1614973}
90 */
91const map = (...fns) => cpsFun => {
92 let cpsNew = (...cbs) => cpsFun(
93 // precompose every callback with fn matched by index or pass directly the args
94 // collect functions in array and pass as callbacks to cpsFun
95 ...cbs.map(
96 (cb, idx) => (...args) => fns[idx] ? cb(fns[idx](...args)) : cb(...args)
97 )
98 )
99 inheritState(cpsNew, cpsFun)
100 return cpsNew
101}
102
103
104/**
105 * Chains outputs of CPS function with arbitrary tuple of other CPS functions,
106 * where the nth function applies to each output of the nth callback
107 * and the resulting outputs are gathered by index
108 *
109 * @signature (...cpsFns) -> CPS -> CPS (curried)
110 *
111 * @name CPS.chain
112 * @params {Tuple of CPS Functions} (...cpsFns)
113 * @curriedParam {CPS Function} cpsFun
114 * @returns {CPS Function} CPS.chain(...fns)(cpsFun)
115 * whose nth callback's output is gathered from
116 * the nth callbacks' outputs of each function fns[j] for each j
117 * evaluated for each output of the jth callback of `cpsFun`
118 *
119 * # Example
120 *
121 * const cpsFun = (cb1, cb2) => cb1(2, 3) + cb2(7, 9)
122 * 2 callbacks receive outputs (2, 3) and (7, 9)
123 * const cpsF1 = (x, y) => (cb1, cb2) => cb1(x + y) + cb2(x - y)
124 * const cpsF2 = (x, y) => cb => cb(x, -y)
125 * CPS.chain(cpsF1, cpsF2)(cpsFun)
126 * is equivalent to the CPS function
127 * (cb1, cb2) => cb1(5) + cb2(7, -9)
128 */
129const chain = (...cpsFns) => cpsFun => {
130 let cpsNew = (...cbs) => cpsFun(
131 // precompose every callback with fn matched by index or pass directly the args,
132 // collect functions in array and pass as callbacks to cpsFun
133 ...cpsFns.map(
134 // all callbacks from the chain get passed to each cpsFn
135 cpsFn => (...args) => cpsFn(...args)(...cbs)
136 )
137 )
138 inheritState(cpsNew, cpsFun)
139 return cpsNew
140}
141
142
143// pass through only input truthy `pred`
144// const cpsFilter = pred => (...input) => cb => {
145// if (pred(...input)) cb(...input)
146// }
147
148// call `chain` with the list of arguments, one per each predicate
149const filter = (...preds) =>
150 chain(...preds.map((pred, idx) =>
151 (...input) => (...cbs) => {
152 if (pred(...input)) cbs[idx](...input)
153 }
154 ))
155
156
157/**
158 * Iterates tuple of reducers over tuple of states
159 * and outputs from CPS function regarded as actions.
160 * `reducers` and `states` are matched by index.
161 *
162 * @signature (...reducers) -> (...states) -> cpsAction -> cpsState
163 *
164 * @name CPS.scan
165 * @params {Tuple of functions} (...reducers)
166 * where @signature of each reducer is (state, ...actions) -> state
167 * @params {Tuple of values} (...states)
168 * @param {CPS function} cpsAction
169 * @returns {CPS function} CPS.scan(...reducers)(...initStates)(cpsAction)
170 * whose nth callback receives the outputs obtained by iterating
171 * the stream of outputs from the nth callback of cpsAction
172 * over reducers[n] starting from with initStates[n]
173 *
174 */
175const scan = (...reducers) => (...initStates) => {
176 let states = initStates
177 // chain cpsAction with tuple of CPS function
178 return cpsAction => pipeline(cpsAction)(
179 // chaining outputs of cpsAction with multiple reducers, one per state
180 chain(
181 // chain receives tuple of functions, one per reducer
182 ...reducers.map((reducer, idx) =>
183 // nth CPS function inside chain receives nth callback output of cpsAction
184 (...action) => (...cbs) => {
185 // accessing states and reducers by index
186 // (undefined === states[idx]) && (states[idx] = initStates[idx])
187 states[idx] = reducer(states[idx], ...action)
188 cbs[idx]( states[idx] )
189 }
190 )
191 ))
192}
193
194
195
196
197/* ----- CPS methods ----- */
198
199const apply2this = fn =>
200 function(...args) {return fn(...args)(this)}
201
202// apply function to all values of object
203const objMap = fn => obj =>
204 Object.keys(obj).reduce((acc, key) => {
205 acc[key] = fn(obj[key])
206 return acc
207 }, {})
208
209// Prototype methods
210const protoObj = objMap(apply2this)({
211 map,
212 chain
213})
214
215const CPS = cpsFn => {
216 // clone the function
217 let cpsWrapped = (...args) => cpsFn(...args)
218 Object.setPrototypeOf(cpsWrapped, protoObj)
219 return cpsWrapped
220}
221
222module.exports = { pipeline, of, map, chain, filter, scan, CPS }