UNPKG

9.07 kBMarkdownView Raw
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
17Tiny but powerful goodies for Continuation-Passing-Style (CPS) functions
18
19```sh
20npm install tiny-cps
21```
22*No dependency policy.*
23For maximum security, this package is intended not to have dependencies ever.
24
25## CPS function
26Any function
27```js
28//cb1, cb2, ... are called any number of times with any
29// (possibly varying each time) number of arguments
30const cpsFn = (cb1, cb2, ...) => { ... }
31```
32that 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
35cbn(x1, ..., xm) // is called
36```
37In 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
42const { map, chain, filter, scan, CPS, pipeline }
43 = require('tiny-cps')
44```
45Each of the `map`, `chain`, `filter`, `scan` operators can be used in 3 ways:
46```js
47// 'map' as curried function
48map(f)(cpsFn)
49// 'map' method provided by the 'CPS' wrapper
50CPS(cpsFn).map(f)
51// 'cpsFn' is piped into 'map(f)' via 'pipeline' operator
52pipeline(cpsFn)(map(f))
53```
54The 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
56CPS(cpsFn)(f1, f2, ...) // is equivalent to
57cpsFn(f1, f2, ...)
58```
59
60#### chaining
61```js
62// as methods
63CPS(cpsFn).map(f).chain(g).filter(h)
64
65// of as functional pipeline
66pipeline(cpsFn)(
67 map(f),
68 chain(g),
69 filter(h)
70)
71```
72
73### `map(...functions)(cpsFunction)`
74```js
75map(f1, f2, ...)(cpsFn)
76CPS(cpsFn).map(f1, f2, ...)
77pipeline(cpsFn)(map(f1, f2, ...))
78```
79For each `n`, apply `fn` to each output from the `n`th callback of `cpsFn`.
80
81#### Result of applying `map`
82New CPS function that calls its `n`th callback `cbn` as
83```js
84cbn(fn(x1, x2, ...))
85```
86whenever `cpsFn` calls its `n`th callback.
87
88#### Example of `map`
89```js
90const fs = require('fs')
91const readFile = (file, encoding) =>
92 cb => fs.readFile(file, encoding, cb) // CPS function
93
94// read file and convert all letters to uppercase
95const getCaps = map(str => str.toUpperCase())(
96 readFile('message.txt', 'utf8')
97)
98// or
99const getCaps = CPS(readFile('message.txt', 'utf8'))
100 .map(str => str.toUpperCase())
101// or
102const getCaps = pipeline(readFile('message.txt', 'utf8'))(
103 map(str => str.toUpperCase())
104)
105
106// getCaps is CPS function, call with any callback
107getCaps((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
115chain(f1, f2, ...)(cpsFn)
116CPS(cpsFn).chain(f1, f2, ...)
117pipeline(cpsFn)(chain(f1, f2, ...))
118```
119where each `fn` is a curried function
120```js
121// fn(x1, x2, ...) is expected to return a CPS function
122const fn = (x1, x2, ...) => (cb1, cb2, ...) => { ... }
123```
124The `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`
127New 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
129cbm(y1, y2, ...) // is called whenever
130cbmFn(y1, y2, ...) // is called where
131// cbmFn is the mth callback of fn
132```
133
134#### Example of `chain`
135```js
136const writeFile = (file, encoding, content) =>
137 // CPS function
138 cb => fs.readFile(file, encoding, content, cb)
139
140const 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
147const copy = CPS(readFile('source.txt', 'utf8'))
148 .chain(text => writFile('target.txt', 'utf8', text))
149// or
150const 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
155copy((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
163filter(pred1, pred2, ...)(cpsFn)
164CPS(cpsFn).filter(pred1, pred2, ...)
165pipeline(cpsFn)(filter(pred1, pred2, ...))
166```
167where 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`
170New 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
172predn(x1, x2, ...) == true
173```
174
175#### Example of `filter`
176```js
177// only copy text if it is not empty
178const 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
183copyNotEmpty(err => console.error(err))
184```
185
186### `scan(...reducers)(...initialValues)(cpsFunction)`
187Similar 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
189scan(red1, red2, ...)(x1, x2, ...)(cpsFn)
190(cpsFn).scan(red1, red2, ...)(x1, x2, ...)
191pipeline(cpsFn)(scan(red1, red2, ...)(x1, x2, ...))
192```
193where each `redn` is a *reducer*
194```
195// compute new accumulator value from the old one
196// and the tuple of current values (y1, y2, ...)
197const redn = (acc, y1, y2, ...) => ...
198```
199
200#### Result of applying `scan`
201New 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
208const getVotes = (onUpvote, onDownvote) => {
209 upvoteButton.addEventListener('click',
210 ev => onUpvote(1)
211 )
212 downvoteButton.addEventListener('click',
213 ev => onDownvote(1)
214 )
215}
216const add = (acc, x) => acc + x
217// count numbers of up- and downvotes and
218// pass into respective callbacks
219const countVotes = scan(add, add)(0, 0)(getVotes) // or
220const countVotes = CPS(getVotes).scan(add, add)(0, 0)
221
222// countVotes is CPS function that we can call
223// with any pair of callbacks
224countVotes(
225 upvotes => console.log(upvotes, ' votes for'),
226 downvotes => console.log(downvotes, ' votes against'),
227)
228```
229
230
231## More details?
232This `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