1 | # tiny-cps
|
2 |
|
3 | [![npm version](https://img.shields.io/npm/v/tiny-cps.svg)](http://npm.im/tiny-cps)
|
4 | [![install size](https://packagephobia.now.sh/badge?p=tiny-cps)](https://packagephobia.now.sh/result?p=tiny-cps)
|
5 | [![bundlephobia](https://img.shields.io/bundlephobia/minzip/tiny-cps.svg)](https://bundlephobia.com/result?p=tiny-cps)
|
6 | [![Build Status](https://travis-ci.org/dmitriz/tiny-cps.svg?branch=master)](https://travis-ci.org/dmitriz/tiny-cps)
|
7 | [![coveralls](https://coveralls.io/repos/github/dmitriz/tiny-cps/badge.svg?branch=master)](https://coveralls.io/github/dmitriz/tiny-cps?branch=master)
|
8 | [![codecov](https://codecov.io/gh/dmitriz/tiny-cps/branch/master/graph/badge.svg)](https://codecov.io/gh/dmitriz/tiny-cps)
|
9 | [![CodeFactor](https://www.codefactor.io/repository/github/dmitriz/tiny-cps/badge)](https://www.codefactor.io/repository/github/dmitriz/tiny-cps)
|
10 | [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/dmitriz/tiny-cps.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/dmitriz/tiny-cps/context:javascript)
|
11 | [![dependencies](https://david-dm.org/dmitriz/tiny-cps.svg)](https://david-dm.org/dmitriz/tiny-cps)
|
12 | [![devDependencies](https://badgen.now.sh/david/dev/dmitriz/tiny-cps)](https://david-dm.org/dmitriz/tiny-cps?type=dev)
|
13 | [![Known Vulnerabilities](https://snyk.io/test/github/dmitriz/tiny-cps/badge.svg?targetFile=package.json)](https://snyk.io/test/github/dmitriz/tiny-cps?targetFile=package.json)
|
14 | [![MIT License](https://img.shields.io/npm/l/tiny-cps.svg?style=flat-square)](http://opensource.org/licenses/MIT)
|
15 | [![Greenkeeper badge](https://badges.greenkeeper.io/dmitriz/tiny-cps.svg)](https://greenkeeper.io/)
|
16 |
|
17 | Tiny but powerful goodies for Continuation-Passing-Style (CPS) functions
|
18 |
|
19 | ```sh
|
20 | npm install tiny-cps
|
21 | ```
|
22 | *No dependency policy.*
|
23 | For maximum security, this package is intended not to have dependencies ever.
|
24 |
|
25 | ## CPS function
|
26 | Any function
|
27 | ```js
|
28 | //cb1, cb2, ... are called any number of times with any
|
29 | // (possibly varying each time) number of arguments
|
30 | const cpsFn = (cb1, cb2, ...) => { ... }
|
31 | ```
|
32 | that expects to be called with several (possibly zero) functions (callbacks) as arguments. The number of callbacks may vary each time `cpsFn` is called. Once called and running, `cpsFn` may call any of the callbacks `cbn` any (possibly zero) number of times with any number `m` of arguments `(x1, ..., xm)`, where `m` may also vary from call to call. The `m`-tuple (vector) `(x1, ..., xm)` is regarded as the *output* of `cpsFn` passed to the `n`the callback:
|
33 | ```js
|
34 | // (x1, ..., xm) is output from nth callback whenever
|
35 | cbn(x1, ..., xm) // is called
|
36 | ```
|
37 | In other words, a CPS function receives any number of callbacks that it may call in any order any number of times at any moments immediately or in the future with any number of arguments.
|
38 |
|
39 |
|
40 | ## API in brief
|
41 | ```js
|
42 | const { map, chain, filter, scan, CPS, pipeline }
|
43 | = require('tiny-cps')
|
44 | ```
|
45 | Each of the `map`, `chain`, `filter`, `scan` operators can be used in 3 ways:
|
46 | ```js
|
47 | // 'map' as curried function
|
48 | map(f)(cpsFn)
|
49 | // 'map' method provided by the 'CPS' wrapper
|
50 | CPS(cpsFn).map(f)
|
51 | // 'cpsFn' is piped into 'map(f)' via 'pipeline' operator
|
52 | pipeline(cpsFn)(map(f))
|
53 | ```
|
54 | The wrapped CPS function `CPS(cpsFn)` has all operators available as methods, while it remains plain CPS function, i.e. can be called with the same callbacks:
|
55 | ```js
|
56 | CPS(cpsFn)(f1, f2, ...) // is equivalent to
|
57 | cpsFn(f1, f2, ...)
|
58 | ```
|
59 |
|
60 | #### chaining
|
61 | ```js
|
62 | // as methods
|
63 | CPS(cpsFn).map(f).chain(g).filter(h)
|
64 |
|
65 | // of as functional pipeline
|
66 | pipeline(cpsFn)(
|
67 | map(f),
|
68 | chain(g),
|
69 | filter(h)
|
70 | )
|
71 | ```
|
72 |
|
73 | ### `map(...functions)(cpsFunction)`
|
74 | ```js
|
75 | map(f1, f2, ...)(cpsFn)
|
76 | CPS(cpsFn).map(f1, f2, ...)
|
77 | pipeline(cpsFn)(map(f1, f2, ...))
|
78 | ```
|
79 | For each `n`, apply `fn` to each output from the `n`th callback of `cpsFn`.
|
80 |
|
81 | #### Result of applying `map`
|
82 | New CPS function that calls its `n`th callback `cbn` as
|
83 | ```js
|
84 | cbn(fn(x1, x2, ...))
|
85 | ```
|
86 | whenever `cpsFn` calls its `n`th callback.
|
87 |
|
88 | #### Example of `map`
|
89 | ```js
|
90 | const fs = require('fs')
|
91 | const readFile = (file, encoding) =>
|
92 | cb => fs.readFile(file, encoding, cb) // CPS function
|
93 |
|
94 | // read file and convert all letters to uppercase
|
95 | const getCaps = map(str => str.toUpperCase())(
|
96 | readFile('message.txt', 'utf8')
|
97 | )
|
98 | // or
|
99 | const getCaps = CPS(readFile('message.txt', 'utf8'))
|
100 | .map(str => str.toUpperCase())
|
101 | // or
|
102 | const getCaps = pipeline(readFile('message.txt', 'utf8'))(
|
103 | map(str => str.toUpperCase())
|
104 | )
|
105 |
|
106 | // getCaps is CPS function, call with any callback
|
107 | getCaps((err, data) => err
|
108 | ? console.error(err)
|
109 | : console.log(data)
|
110 | ) // => file content is capitalized and printed
|
111 | ```
|
112 |
|
113 | ### `chain(...functions)(cpsFunction)`
|
114 | ```js
|
115 | chain(f1, f2, ...)(cpsFn)
|
116 | CPS(cpsFn).chain(f1, f2, ...)
|
117 | pipeline(cpsFn)(chain(f1, f2, ...))
|
118 | ```
|
119 | where each `fn` is a curried function
|
120 | ```js
|
121 | // fn(x1, x2, ...) is expected to return a CPS function
|
122 | const fn = (x1, x2, ...) => (cb1, cb2, ...) => { ... }
|
123 | ```
|
124 | The `chain` operator applies each `fn` to each output from the `n`th callback of `cpsFn`, however, the CPS *ouptup* of `fn` is passed ahead instead of the return value.
|
125 |
|
126 | #### Result of applying `chain`
|
127 | New CPS function `newCpsFn` that calls `fn(x1, x2, ...)` whenever `cpsFn` passes output `(x1, x2, ...)` into its `n`th callback, and collects all outputs from all callbacks of all `fn`s. Then for each fixed `m`, outputs from the `m`th callbacks of all `fn`s are collected and passed into the `m`th callback `cbm` of `newCpsFn`:
|
128 | ```js
|
129 | cbm(y1, y2, ...) // is called whenever
|
130 | cbmFn(y1, y2, ...) // is called where
|
131 | // cbmFn is the mth callback of fn
|
132 | ```
|
133 |
|
134 | #### Example of `chain`
|
135 | ```js
|
136 | const writeFile = (file, encoding, content) =>
|
137 | // CPS function
|
138 | cb => fs.readFile(file, encoding, content, cb)
|
139 |
|
140 | const copy = chain(
|
141 | // function that returns CPS function
|
142 | text => writFile('target.txt', 'utf8', text)
|
143 | )(
|
144 | readFile('source.txt', 'utf8') // CPS function
|
145 | )
|
146 | // or
|
147 | const copy = CPS(readFile('source.txt', 'utf8'))
|
148 | .chain(text => writFile('target.txt', 'utf8', text))
|
149 | // or
|
150 | const copy = pipeline(readFile('source.txt', 'utf8'))(
|
151 | chain(text => writFile('target.txt', 'utf8', text))
|
152 | )
|
153 |
|
154 | // copy is a CPS function, call it with any callback
|
155 | copy((err, data) => err
|
156 | ? console.error(err)
|
157 | : console.log(data)
|
158 | ) // => file content is capitalized and printed
|
159 | ```
|
160 |
|
161 | ### `filter(...predicates)(cpsFunction)`
|
162 | ```js
|
163 | filter(pred1, pred2, ...)(cpsFn)
|
164 | CPS(cpsFn).filter(pred1, pred2, ...)
|
165 | pipeline(cpsFn)(filter(pred1, pred2, ...))
|
166 | ```
|
167 | where each `predn` is the `n`th predicate function used to filter output from the `n`th callback of `cpsFn`.
|
168 |
|
169 | #### Result of applying `chain`
|
170 | New CPS function that calls its `n`th callback `cbn(x1, x2, ...)` whenever `(x1, x2, ...)` is an output from the `n`th callback of `cpsFun` and
|
171 | ```js
|
172 | predn(x1, x2, ...) == true
|
173 | ```
|
174 |
|
175 | #### Example of `filter`
|
176 | ```js
|
177 | // only copy text if it is not empty
|
178 | const copyNotEmpty = CPS(readFile('source.txt', 'utf8'))
|
179 | .filter(text => text.length > 0)
|
180 | .chain(text => writFile('target.txt', 'utf8', text))
|
181 |
|
182 | // copyNotEmpty is CPS function, call with any callback
|
183 | copyNotEmpty(err => console.error(err))
|
184 | ```
|
185 |
|
186 | ### `scan(...reducers)(...initialValues)(cpsFunction)`
|
187 | 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.
|
188 | ```js
|
189 | scan(red1, red2, ...)(x1, x2, ...)(cpsFn)
|
190 | (cpsFn).scan(red1, red2, ...)(x1, x2, ...)
|
191 | pipeline(cpsFn)(scan(red1, red2, ...)(x1, x2, ...))
|
192 | ```
|
193 | where each `redn` is a *reducer*
|
194 | ```
|
195 | // compute new accumulator value from the old one
|
196 | // and the tuple of current values (y1, y2, ...)
|
197 | const redn = (acc, y1, y2, ...) => ...
|
198 | ```
|
199 |
|
200 | #### Result of applying `scan`
|
201 | 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.
|
202 |
|
203 |
|
204 | #### Example of `scan`
|
205 | ```js
|
206 | // CPS function with 2 callbacks, a click on one
|
207 | // of the buttons sends '1' into respective callback
|
208 | const getVotes = (onUpvote, onDownvote) => {
|
209 | upvoteButton.addEventListener('click',
|
210 | ev => onUpvote(1)
|
211 | )
|
212 | downvoteButton.addEventListener('click',
|
213 | ev => onDownvote(1)
|
214 | )
|
215 | }
|
216 | const add = (acc, x) => acc + x
|
217 | // count numbers of up- and downvotes and
|
218 | // pass into respective callbacks
|
219 | const countVotes = scan(add, add)(0, 0)(getVotes) // or
|
220 | const countVotes = CPS(getVotes).scan(add, add)(0, 0)
|
221 |
|
222 | // countVotes is CPS function that we can call
|
223 | // with any pair of callbacks
|
224 | countVotes(
|
225 | upvotes => console.log(upvotes, ' votes for'),
|
226 | downvotes => console.log(downvotes, ' votes against'),
|
227 | )
|
228 | ```
|
229 |
|
230 |
|
231 | ## More details?
|
232 | This `README.md` is kept minimal to reduce the package size. For more human introduction, motivation, use cases and other details, please see [DOCUMENTATION](DOCUMENTATION.md).
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|