1 | // Helper to inherit the prototype
|
2 | const 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 | */
|
29 | const 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 | */
|
58 | const 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 | */
|
91 | const 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 | */
|
129 | const 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
|
149 | const 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 | */
|
175 | const 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 |
|
199 | const apply2this = fn =>
|
200 | function(...args) {return fn(...args)(this)}
|
201 |
|
202 | // apply function to all values of object
|
203 | const objMap = fn => obj =>
|
204 | Object.keys(obj).reduce((acc, key) => {
|
205 | acc[key] = fn(obj[key])
|
206 | return acc
|
207 | }, {})
|
208 |
|
209 | // Prototype methods
|
210 | const protoObj = objMap(apply2this)({
|
211 | map,
|
212 | chain
|
213 | })
|
214 |
|
215 | const CPS = cpsFn => {
|
216 | // clone the function
|
217 | let cpsWrapped = (...args) => cpsFn(...args)
|
218 | Object.setPrototypeOf(cpsWrapped, protoObj)
|
219 | return cpsWrapped
|
220 | }
|
221 |
|
222 | module.exports = { pipeline, of, map, chain, filter, scan, CPS }
|