UNPKG

65.3 kBMarkdownView Raw
1# Tiny-Cps
2Tiny but powerful goodies for Continuation-Passing-Style functions
3
4> Simplicity is prerequisite for reliability.
5> Elegance is not a dispensable luxury but a factor that decides between success and failure.
6>
7> --- [Edsger W. Dijkstra](https://www.azquotes.com/author/3969-Edsger_Dijkstra)
8
9>
10```js
11// ignorant
12const getServerStuff = callback => ajaxCall(json => callback(json))
13// enlightened
14const getServerStuff = ajaxCall
15```
16*--- From [Mostly adequate guide to Functional Programming](https://github.com/MostlyAdequate/mostly-adequate-guide).*
17
18
19- [CPS functions](#cps-functions)
20 * [Why?](#why)
21 * [Advanced composability](#advanced-composability)
22 * [What is new here?](#what-is-new-here)
23 + [Variadic input and output](#variadic-input-and-output)
24 + [Full power of multiple outputs streams](#full-power-of-multiple-outputs-streams)
25 + [Functional progamming paradigm](#functional-progamming-paradigm)
26 + [Lazy or eager?](#lazy-or-eager)
27 + [Differences with Haskell](#differences-with-haskell)
28 + ["Do less" is a feature](#do-less-is-a-feature)
29 * [Terminology](#terminology)
30 * [Using CPS functions](#using-cps-functions)
31 * [What about Callback Hell?](#what-about-callback-hell)
32 * [Asynchronous iteration over array](#asynchronous-iteration-over-array)
33- [Examples of CPS functions](#examples-of-cps-functions)
34 * [Promise producers](#promise-producers)
35 * [Promises](#promises)
36 * [Node API](#node-api)
37 * [HTTP requests](#http-requests)
38 * [Database Access](#database-access)
39 * [Middleware e.g. in Express or Redux](#middleware-eg-in-express-or-redux)
40 * [Web Sockets](#web-sockets)
41 * [Stream libraries](#stream-libraries)
42 + [Pull Streams](#pull-streams)
43 + [Flyd](#flyd)
44 * [Event aggregation](#event-aggregation)
45- [Comparison with Promises and Callbacks](#comparison-with-promises-and-callbacks)
46 * [Returning results](#returning-results)
47 * [Chaining](#chaining)
48 * [Asynchronous composition](#asynchronous-composition)
49 * [Error handling](#error-handling)
50 * [Signatures](#signatures)
51 * [Standardization](#standardization)
52- [Functional and Fluent API](#functional-and-fluent-api)
53 * [Conventions](#conventions)
54 * [CPS.map](#cpsmap)
55 + [Mapping over single function](#mapping-over-single-function)
56 + [Mapping over multiple functions](#mapping-over-multiple-functions)
57 + [Map taking multiple arguments](#map-taking-multiple-arguments)
58 + [Functor laws](#functor-laws)
59 + [CPS.of](#cpsof)
60 * [CPS.chain](#cpschain)
61 + [Transforming multiple arguments into multiple arguments](#transforming-multiple-arguments-into-multiple-arguments)
62 + [Why is it called `chain`?](#why-is-it-called-chain)
63 + [Composing multiple outputs](#composing-multiple-outputs)
64 + [Passing multiple CPS functions to `chain`](#passing-multiple-cps-functions-to-chain)
65 + [Monadic laws](#monadic-laws)
66 - [Associativity law](#associativity-law)
67 - [Identity laws](#identity-laws)
68 * [Application of `chain`: Turn Node API into Promise style callbacks](#application-of-chain-turn-node-api-into-promise-style-callbacks)
69 * [CPS.ap](#cpsap)
70 + [Running CPS functions in parallel](#running-cps-functions-in-parallel)
71 + [Lifting functions of multiple parameters](#lifting-functions-of-multiple-parameters)
72 - [Promise.all](#promiseall)
73 - [Usage notes](#usage-notes)
74 + [Applying multiple functions inside `ap`](#applying-multiple-functions-inside-ap)
75 + [Applicative laws](#applicative-laws)
76 * [CPS.merge](#cpsmerge)
77 + [Relation with Promise.race](#relation-with-promiserace)
78 + [Commutative Monoid](#commutative-monoid)
79 * [CPS.filter](#cpsfilter)
80 + [Filtering over multiple functions](#filtering-over-multiple-functions)
81 + [Implementation via `chain`](#implementation-via-chain)
82 * [CPS.scan](#cpsscan)
83
84
85
86# CPS functions
87
88> [The Mother of all Monads](https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/the-mother-of-all-monads)
89
90
91
92## Why?
93Functions are the most basic and powerful concept.
94A whole program can be written as funciton,
95taking input data and producing output.
96However, viewing function's return value as the only output is often too limited.
97For instance, all asynchronous Node API methods rely on the output data
98returned via callbacks rather than via functions' return values.
99This patter is of course the well-known
100[Continuation-Passing Style (CPS)](https://en.wikipedia.org/wiki/Continuation-passing_style)
101
102
103## Advanced composability
104The famous article by John Backus ["Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs"](https://www.cs.ucf.edu/~dcm/Teaching/COT4810-Fall%202012/Literature/Backus.pdf)
105advocated to "reduce the code obesity" by building generic hierarchical ways of composing entire programs.
106The present proposal attempts to provide some way of how such composability can be achieved.
107
108
109## What is new here?
110Traditionally Continuation-Passing Style is implemented
111via callbacks as part of the function's parameters:
112```js
113const api = (input, callback) => doSomeWork(input, callback)
114```
115A fundamental problem here is that the input and output data are getting mixed
116among function's parameters, making it hard to separate one from another.
117
118Our main proposal is to solve this problem via currying:
119```js
120const api = input => callback => doSomeWork(input, callback)
121```
122Now the output is cleanly separated from the input via the function's curried signature.
123Further parameters can easily be added to the input:
124```js
125const api = (input1, input2, ...) => callback => doSomeWork(input1, ..., callback)
126```
127as well as to the callbacks accepting output:
128```js
129const api = (input1, input2, ...) => (callback1, callbacks2, ...) =>
130 doSomeWork(input1, ... , callback1, ...)
131```
132
133### Variadic input and output
134JavaScript's functions are variadic by design,
135that is, are capable of accepting arbitrary number of arguments at the runtime.
136That feature makes it very convenient and powerful to implement optional parameters or set defaults:
137```js
138const f = (required, optionalWithDefault = default, iAmOptional) => { ... }
139```
140Now, given the clean separation provided by currying as mentioned above,
141we get for free the full functional variadic power provded by JS:
142```js
143const api = (...inputs) => (...callbacks) => doSomeWork(inputs, callbacks)
144```
145Here `...inputs` is the array holding all arguments passed to the function
146at the run time, by means of the [Rest parameters syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters).
147In particular, zero arguments are also allowed on each side.
148
149
150### Full power of multiple outputs streams
151By its design, JavaScript's function can call
152any of its callbacks arbitrarily many times at arbitrary moments.
153This provides a simple implementation of multiple data streams
154emitted from a single function.
155Each stream value is passed as arguments of the callback,
156that is, a whole list of values can be emitted at the same time
157as arguments of the same function call.
158
159
160### Functional progamming paradigm
161The proposed curried design rests on the well-known paradigms.
162It generalizes the [Kleisli arrows](https://en.wikipedia.org/wiki/Kleisli_category)
163`a -> m b` associated to the Monad `m`.
164In our case, the Continuation Monad `m b` corresponds to passing single `callback` function
165```js
166const monad = callback => computation(callback)
167```
168and can be regarded as a "suspended computation".
169The Monad structure is provided via the `of` and `chain` methods
170(aka `return` and `bind` in Haskell, or `unit` and `flatMap` in Scala), see below.
171As part of the variadic functionality, we generalize these Monadic methods
172by allowing for arbitrary number of function arguments that are matched against the callbacks, see below.
173This allows for easy handling of multiple output streams with single methods.
174
175In addition to generalized Monadic methods dealing with sequential computations,
176generalized Applicative `ap` and derived `lift` are dealing with parallel ones.
177As well as Monoidal method `merge` dealing with merging multiple streams.
178See the paper by [Conal Elliot, "Push-Pull Functional Reactive Programming"](http://conal.net/papers/push-pull-frp/push-pull-frp.pdf).
179
180
181### Lazy or eager?
182The lazy vs eager functionality is already built in the function design:
183```js
184const cpsFun = input => callback => doSomeWork(callback)
185// lazy - waiting to be called
186cpsFun(input)
187// eager - running with the callback passed
188cpsFun(input)(callback)
189```
190Both are of course just functions and function calls, and can be used depending on the need.
191
192
193### Differences with Haskell
194Functional Programming in JavaScript has been largely influenced by Haskell.
195However, there are fundamental design differences with Haskell:
196
197#### JavaScript functions are by design not required to be [pure](https://en.wikipedia.org/wiki/Pure_function)
198
199While one can always restrict to pure functions only, the available design allows to treat all functions uniformly, including non-pure ones. That provides considerable additional power at no extra cost. As a basic example, consider non-pure function mutating a variable
200```js
201var a = 0
202const f = x => {
203 x = x + a
204}
205```
206that can be (pre-)composed with any other function `g`:
207```js
208const g => y => y * 2
209const composed = y => f(g(x))
210// or equivalently in functional way
211const compose = (f,g) => x => f(g(x))
212const composed = compose(f,g)
213```
214The `compose` operator is defined in uniform fashion and thus allows to compose arbitrary non-pure funcitons without any extra cost.
215
216
217
218#### JavaScript functions are by design accepting arbitrary number of arguments
219
220Again, one can always restrict to single argument, but that way considerable additional power provided by the language design is lost. For instance, object methods (that in JavaScript are treated as regular functions) are often defined with no parameters. As basic example consider adding results of two separate computations:
221```js
222const f1 = x => someComputation1(x)
223const f2 = y => someComputation2(y)
224const add = (a, b) => a + b
225// binary addition is (pre-)composed with both f1, f2
226const result = (x, y) => add(f1(x), f2(y))
227```
228Defining such abstract composition operator is straightforward:
229```js
230const binaryCompose => (h, f1, f2) => (x, y) => h(f1(x), f2(y))
231const result = binaryCompose(add, f1, f2)
232```
233However, all 3 parameters `h, f1, f2` are mixed inside the signature, despite of their different roles. It is difficult to remember which function goes where and easy to introduce errors. A more readable and expressive way would be to use the curried signature:
234```js
235const binaryCompose1 => h => (f1, f2) => (x, y) => h(f1(x), f2(y))
236const result = binaryCompose1(add)(f1, f2)
237```
238Now the inside functions `f1, f2` are visibly separated from the outside `h`.
239The logic is much cleaner, probability of errors is lower and function is easier to test and debug. Such convenient separation between groups of functional parameters is easier in JavaScript than e.g. in Haskell with no distinction between curried and uncurried parameters.
240
241
242### "Do less" is a feature
243The proposed CPS desing API is minimal and focused on doing just one thing --
244*a style to write and combine plain JavaScript funcitons with callbacks*.
245
246
247## Terminology
248A *Continuation-Passing-Style (CPS) function* is any function
249```js
250const cps = (f1, f2, ...) => {
251 /* f1, f2, ... are called arbitrarily often with any number of arguments */
252}
253```
254that expects to be called with zero or several functions as arguments.
255By *expects* we mean that this library and the following discussion
256only applies when functions are passed.
257In a strictly typed language that would mean those arguments are required to be functions.
258However, in JavaScript, where it is possible to pass any argument,
259we don't aim to force errors when some arguments passed are not functions
260and let the standard JavaScript engine deal with it the usual way,
261as per [garbage in, garbage out (GIGO)](https://en.wikipedia.org/wiki/Garbage_in,_garbage_out) principle.
262
263We also call the argument functions `f1, f2, ...` "callbacks"
264due to the way how they are used.
265Each of the callbacks can be called arbitrarily many times or never,
266with zero to many arguments each time.
267The number of arguments inside each callback
268can change from call to call and is even allowed to unlimitedly grow,
269e.g. `n`th call may involve passing `n` arguments.
270
271By a *parametrized CPS function* we mean any curried function with zero or more parameters
272that returns a CPS function:
273```js
274const paramCps = (param1, param2, ...) => (f1, f2, ...) => { ... }
275```
276
277We shall adopt somewhat loose terminology calling *parametrized CPS functions* both
278the curried function `paramCps` and its return value `paramCps(params)`,
279in the hope that the context will make clear the precisce meaning.
280In the same vein, by a *function call* of the parametrized CPS function,
281we mean its call with both arguments and callbacks passed:
282```js
283paramCps(...args)(...callbacks)
284```
285Otherwise `parmCps(...args)` is considered a *partial call*.
286
287
288## Using CPS functions
289Using CPS functions is as simple as using JavaScript Promises:
290```js
291// Set up database query as parametrized CPS function with 2 callbacks,
292// one for the result and one for the error
293const cpsQuery = query => (resBack, errBack) =>
294 // assuming Node style callback with error param first
295 queryDb(query, (err, res) => err
296 ? resBack(res)
297 : errBack(err))
298// Now just call as regular curried function
299cpsQuery({name: 'Jane'})(
300 result => console.log("Your Query returned: ", result),
301 error => console.error("Sorry, here is what happened: ", error)
302)
303```
304The latter is very similar to how Promises are used:
305```js
306promiseQuery({name: 'Jane'}).then(
307 result => console.log("Your query returned: ", result),
308 error => console.error("Sorry, an error happened: ", error)
309)
310```
311Except that, calling `then` method is replaced by plain function call
312and arbitrary number of callbacks is allowed,
313each of which can be called arbitrary many times,
314as e.g. in the event streams.
315A Promise is essentially a CPS function with its first event cached,
316that can be implemented by chaining (via [`chain`](#cpschain), see below) any CPS function
317with the one picking and caching the first output from any callback.
318
319
320## What about Callback Hell?
321There is an actual website called [*Callback Hell*](http://callbackhell.com/).
322The following callback hell example is shown:
323```js
324fs.readdir(source, function (err, files) {
325 if (err) {
326 console.log('Error finding files: ' + err)
327 } else {
328 files.forEach(function (filename, fileIndex) {
329 console.log(filename)
330 gm(source + filename).size(function (err, values) {
331 if (err) {
332 console.log('Error identifying file size: ' + err)
333 } else {
334 console.log(filename + ' : ' + values)
335 aspect = (values.width / values.height)
336 widths.forEach(function (width, widthIndex) {
337 height = Math.round(width / aspect)
338 console.log('resizing ' + filename + 'to ' + height + 'x' + height)
339 this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
340 if (err) console.log('Error writing file: ' + err)
341 })
342 }.bind(this))
343 }
344 })
345 })
346 }
347})
348```
349
350The solution proposed there to avoid this "hell" consists of splitting into mulitple functions and giving names to each.
351However, naming is hard and
352[is not always recommended](https://www.cs.ucf.edu/~dcm/Teaching/COT4810-Fall%202012/Literature/Backus.pdf).
353
354
355Using CPS functions along with `map` and `chain` operators,
356we can break that code into a sequence of small functions, chained one after another
357without the need to name them:
358```js
359// wrap into `CPS` object to have `map` and `chain` methods available,
360// directory files are passed as 2nd argument to cb, error as 1st
361CPS(cb => fs.readdir(source, cb))
362 // chain method passes instead the same 1st and 2nd arguments into the new CPS function
363 .chain((err, files) => cb =>
364 // only files are passed to the callback, whereas error produces no continuation
365 err ? console.log('Error finding files: ' + err) : cb(files)
366 )
367 // chain modifies the CPS by passing `files`` from inside `cb` into the next CPS function instead
368 .chain(files => cb => files.forEach((filename, fileIndex) => {
369 console.log(filename)
370 // make use of the multiple outputs passed to `cb` for each file
371 // simply add `filename` to the optput inside `cb` to be consumed later
372 gm(source + filename).size((err, values) => cb(err, values, filename))
373 }))
374 // now again chain accepts CPS function with the same 3 arguments as previously passed to `cb`
375 .chain((err, values, filename) => cb =>
376 err ? console.log('Error identifying file size: ' + err) : cb(values, filename)
377 )
378 // now we have `values` and `filename` as we need
379 .chain((values, filename) => cb => {
380 console.log(filename + ' : ' + values)
381 aspect = (values.width / values.height)
382 // as before, simply pass to callback `cb`
383 // and handle all outputs in the next `chain` function
384 widths.forEach(cb)
385 })
386 // now that we have called `cb` multiple times, each time chain passes new values to its CPS function
387 .chain((width, widthIndex) => cb => {
388 height = Math.round(width / aspect)
389 console.log('resizing ' + filename + 'to ' + height + 'x' + height)
390 this.resize(width, height).write(dest + 'w' + width + '_' + filename, cb)
391 }.bind(this))
392 // finally errors are handled via map method
393 .map(err => err ? console.log('Error writing file: ' + err) : '')
394
395```
396
397Equivalently, we can use the `pipeline` operator (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Pipeline_operator) to achieve the same result
398in more functional (aka point-free) style:
399
400```js
401pipeline( cb => fs.readdir(source, cb) ) (
402 chain( (err, files) => cb => ... ),
403 chain( files => cb => files.forEach((filename, fileIndex) => ... ) ),
404 chain( (err, values, filename) => cb => ... ),
405 chain( (values, filename) => cb => ... ),
406 chain( (width, widthIndex) => cb => ... ),
407 map( err => err ? console.log('Error writing file: ' + err) : '' ),
408)
409```
410
411In the latter pattern there is no wrapper around the first CPS function,
412it is simply passed around through all the transformations in the sequence.
413
414Any such sequence of computations can be similaly achieved with just two operators - `map` and `chain`.
415In fact, just the single more powerful `chain` is enough, as e.g. the following are equivalent:
416
417```js
418CPS(cpsFun).map((x, y) => f(x, y))
419CPS(cpsFun).chain((x, y) => cb => cb(f(x, y)))
420```
421
422or, equivalently, using the `pipeline` operator
423
424```js
425pipeline(cpsFun)( map((x, y) => f(x, y)) )
426pipeline(cpsFun)( chain((x, y) => cb => cb(f(x, y)) )
427```
428
429A limitation of the `chain` is its sequential nature.
430To run computations in parallel, the `ap` (aka `apply`) operator
431is more suitable, see below.
432
433
434## Asynchronous iteration over array
435On of the functions in the above example illustrates
436how multiple outputs fit nicely in the asynchronous iteration pattern:
437
438```js
439const jobCps = files => cb => files.forEach((filename, fileIndex) => {
440 console.log(filename)
441 gm(source + filename).size((err, values) => cb(err, values, filename))
442})
443```
444
445Here we create the `jobCps` function that accepts callback
446and calls it repeatedly for each `file`.
447That wouldn't work with Promises that can only hold single value each,
448so you would need to create as many Promises as the number of elements in the `file` array.
449Instead, we have a single CPS function as above to hold all the asynchronous outputs for all files!
450
451
452
453# Examples of CPS functions
454
455## Promise producers
456Any producer (aka executor) function
457
458```js
459const producer = function(resolve, reject) {
460 // some work ...
461 if (everythingIsOk) resolve(result)
462 else reject(error)
463}
464```
465
466as one typically passed to the [Promise constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) is an example of a CPS function.
467
468The constructed promise `new Promise(producer)` only keeps the very first call of either of the callbacks,
469whereas the producer function itself may call its callbacks multiple times,
470each of which would be fully retained as output when CPS functions are used instead of promises.
471
472## Promises
473Any JavaScript Promise generates a CPS function via its `.then` method
474that completely captures the information held by the Promise:
475
476```js
477const cpsFromPromise = (onFulfilled, onRejected) =>
478 promise.then(onFulfilled, onRejected)
479```
480
481The important restictions Promises arising that way are:
4821. Among many callbacks passed, at most one is ever called.
4832. Each of the callbacks is called precisely with one argument.
484CPS functions do not have such limitations.
485
486As any Promise provides a CPS function via its `then` method with two callbacks,
487it can be dropped direclty into any CPS operator:
488
489```js
490CPS(cpsFun)
491 .chain((x, y) => somePromise(x, y).then)(
492 res => console.log("Result is: ", res),
493 err => console.err("Something bad happened: ", err)
494 )
495```
496
497Here `(x, y)` is the first output from `cpsFun` (the one passed into the first callback).
498Now every such output will be passed into `somePromise` via [`chain`](#cpschain),
499that will subsequently pass its result or error into the final callbacks
500that are attached via plain function call.
501And even better, the error callbacks will also receive
502all error outputs from `cpsFun`, basically whatever is passed into its second callback.
503The outputs from both functions are simply merged together,
504due to the "flattening" job performed by the `chain`.
505
506Conversely, any CPS function, being just a function accepting callbacks as its arguments,
507can be dropped into the Promise constructor
508(from any Promise implementation) to return the Promise
509holding the first argument from the first output as its resolved value,
510while that from the second callback as error.
511
512
513## Node API
514Any Node-Style function with one of more callbacks can be curried into a parametrized CPS function:
515
516```js
517const readFileCPS = (path, options) => callback => fs.readFile(path, options, callback)
518```
519
520Here `readFileCPS` returns a CPS function for each values of its parameters
521`(path, options)`.
522
523Typically Node API callbacks are called with at least two arguments as
524`callback(error, arg1, ...)`,
525where the first argument is used as indication of error.
526CPS functions generalize this case to arbitrary number of callbacks
527accepting arbitrary number of arguments each.
528
529
530## HTTP requests
531In a similar vein, any HTTP request with callback(s) can be regarded as parametrized CPS function:
532```js
533const request = require('request')
534// the CPS function is just the curried version
535const requestCps = options => callback => http.request(options, callback)
536```
537
538Now `requestCps` is can be composed with any function computing its `options` object, and the output arguments passed to `callback` can be mapped over any function or chained with any other CPS function:
539```js
540const customRequest = pipe (
541 prepareReqObject,
542 requestCps,
543 // args from callback passed to function inside chain
544 chain((err, res, body) => (resCallback, errCallback) => doWork(...))
545)
546```
547
548Or using the [native Node `https.request`](https://nodejs.org/api/https.html#https_https_request_options_callback):
549```js
550const https = require('https')
551const httpsReqCps = (url, options) => cb => http.request(url, options, cb)
552```
553and turning data events to plain CPS function outputs:
554```js
555const dataStreamCps = pipe (
556 httpsReqCps,
557 // attach `cb` as even listener
558 chain(response => cb => response.on('data', cb)),
559 // and handle the data in the next CPS function
560 chain(dataChunk => cb => cb(someTransformation(dataChunk)))
561)
562```
563
564## Database Access
565Any async database access API with callbacks can be curried into parametrized CPS functions:
566
567```js
568const queryDb = (db, query) => callback => getQuery(db, query, callback)
569const insertDb = (db, data) => callback => inserData(db, data, callback)
570```
571
572In most cases each of these is considered a single request resulting in either success of failure.
573However, more general CPS functions can implement more powerful functionality with multiple callback calls.
574For instance, a function can run multiple data insetion attempts with progress reported back to client.
575Or the query function can return its result in multiple chunks, each with a separate callback call. Or even subscribe to changes and update client in real time.
576Further, the database query funtion can hold a state that is advanced with each call.
577Similarly, any database access can be cancelled by subsequent call of the same CPS function with suitable parameters.
578
579## Middleware e.g. in Express or Redux
580The [Express Framework](https://expressjs.com/) in NodeJs popularised
581the concept of [middleware](https://expressjs.com/en/guide/writing-middleware.html)
582that later found its place in other frameworks such as
583[Redux](https://redux.js.org/advanced/middleware#understanding-middleware).
584In each case, a *middleware* is a special kind of function,
585plain in case of Express and curried in case of Redux,
586which has one continuation callback among its parameters.
587To each middleware in each of these frameworks,
588there is the associated parametrized CPS function,
589obtained by switching parameters and (un)currying.
590As the correspondence `middleware <-> CPS function` goes in both ways,
591it allows for each side to benefit from the other.
592
593
594## Web Sockets
595Here is a generic CPS function parametrized by its url `path`:
596```js
597const WebSocket = require('ws')
598const createWS = path => callback =>
599 new WebSocket(path).on('message', callback)
600```
601
602The callback will be called repeatedly with every new socket message emited.
603
604Other websocket events can be subscribed by other callbacks,
605so that a single CPS function with its multiple callbacks
606can encapsulate the entire socket functionality.
607
608
609## Stream libraries
610
611### Pull Streams
612The [Pull Streams](https://pull-stream.github.io/)
613present an ingenious way of implementing
614a rich on-demand stream functionality,
615including back pressure,
616entirely with plain JavaScript functions.
617In a way, they gave some of the original inspirations
618for the general CPS function pattern.
619
620Indeed, a Pull Stream is essentially a function `f(abort, callback)` that is called repeatedly
621by the sink to produce on-demand stream of data.
622Any such function can be clearly curried into a
623is a parametrized CPS function
624```js
625const pullStream = params => callback => {...}
626```
627
628### [Flyd](https://github.com/paldepind/flyd)
629Any `flyd` stream can be wrapped into a CPS function with single callback called with single argument:
630```js
631const cpsFun = callback => flydStream
632 .map(x => callback(x))
633```
634The resulting CPS function `cpsFun`, when called with any `callback`,
635simply subsribes that callback to the stream events.
636
637Conversely, any CPS function `cpsFun` can be simply called with
638any `flyd` stream in place of one of its callback arguments:
639```js
640let x = flyd.stream()
641cpsFun(x)
642```
643That will push the first argument of any callback call of `x` into the stream.
644
645
646## Event aggregation
647Similarly to [`flyd` streams](https://github.com/paldepind/flyd/#creating-streams),
648CPS functions can subscribe their callbacks to any event listener:
649```js
650const cpsFun = callback =>
651 document.getElementById('button')
652 .addEventListener('click', callback)
653```
654Furthermore, more complex CPS functions can similarly subscribe to
655muiltiple events:
656```js
657const cpsFun = (cb1, cb2) => {
658 document.getElementById('button1')
659 .addEventListener('click', cb1)
660 document.getElementById('button2')
661 .addEventListener('click', cb2)
662}
663```
664and thereby serve as functional event aggregators
665encapsulating multiple events.
666Every time any of the event is emitted,
667the corresponding callback will fire
668with entire event data passed as arguments.
669That way complete information from multiple events
670remains accessible via single CPS function.
671
672
673
674# Comparison with Promises and Callbacks
675
676Our main motivation for dealing with CPS functions is to enhance
677the power of common coding patterns into a single unified abstraction,
678which can capture the advantages typically regarded as ones of Promises over callbacks.
679
680In the [introductory section on Promises](http://exploringjs.com/es6/ch_promises.html#sec_introduction-promises) of his wonderful book [Exploring ES6](http://exploringjs.com/es6/),
681[Dr. Axel Rauschmayer](http://dr-axel.de/) collected a list of
682advantages of Promises over callbacks,
683that we would like to consider here in the light of the CPS functions
684and explain how, in our view, the latters can enjoy the same advantages.
685
686## Returning results
687> No inversion of control: similarly to synchronous code, Promise-based functions return results, they don’t (directly) continue – and control – execution via callbacks. That is, the caller stays in control.
688
689We regard the CPS functions returning their output in similar fashion as promises,
690via the arguments inside each callback call.
691Recall that a result inside promise can only be extracted via a callback,
692which is essentially the same as passing the callback to a CPS function:
693```js
694// pass callbacks to promise
695const promise.then(cb1, cb2)
696// => result is delivered via cb1(result)
697```
698```js
699// pass callbacks to CPS function
700const cps(f1, f2)
701// => a tuple (vector) of results is deliverd via f1(res1, res2, ...)
702```
703Thus, CPS functions can be regarded as generalization of promises,
704where callbacks are allowed to be called multiple times with several arguments each time,
705rather than with a single value.
706Note that syntax for CPS function is even shorter - there is no `.then` method needed.
707
708
709## Chaining
710> Chaining is simpler: If the callback of `then()` returns a Promise (e.g. the result of calling another Promise-based function) then `then()` returns that Promise (how this really works is more complicated and explained later). As a consequence, you can chain then() method calls:
711
712```js
713asyncFunction1(a, b)
714 .then(result1 => {
715 console.log(result1);
716 return asyncFunction2(x, y);
717 })
718 .then(result2 => {
719 console.log(result2);
720 });
721```
722
723In our view, the complexity of chaing for the callbacks is merely due to lacking convenience methods for doing it.
724On a basic level, a Promise wraps a CPS function into an object providing such methods.
725However, the Promise constructor also adds limitations on the functionality and generally does a lot more, sometimes at the cost of performance.
726On the other hand, to have similar chaining methods, much less powerful methods are needed,
727that can be uniformly provided for general CPS functions.
728The above example can then be generalized to arbitrary CPS functions:
729```js
730// wrapper providing methods
731CPS(cpsFunction1(a, b))
732 // 'chain' (aka 'flatMap') is used to compose parametrized CPS functions
733 .chain(result1 => {
734 console.log(result1);
735 return cpsFunction2(x, y);
736 })
737 // 'map' is used to compose CPS outputs with ordinary functions
738 .map(result2 => {
739 console.log(result2);
740 });
741```
742Here `CPS(...)` is a lightweight object wrapper providing the `map` and `chain` methods among others,
743such that `CPS.of` and `map` conform to the [Pointed Functor](https://stackoverflow.com/questions/39179830/how-to-use-pointed-functor-properly/41816326#41816326) and `CPS.of` with `CPS.chain` to the [Monadic](https://github.com/rpominov/static-land/blob/master/docs/spec.md#monad) [interface](https://github.com/fantasyland/fantasy-land#monad).
744At the same time, the full functional structure is preserved allowing for drop in replacement
745`cpsFun` with `CPS(cpsFun)`, see below.
746
747
748## Asynchronous composition
749> Composing asynchronous calls (loops, mapping, etc.): is a little easier, because you have data (Promise objects) you can work with.
750
751Similar to promises wrapping their data,
752we regard the CPS functions as wrapping the outputs of their callbacks.
753Whenever methods are needed, a CPS function can be explicitly wrapped into
754its CPS object via the `CPS`,
755similar to how the `Promise` constructor wraps its producer function,
756except that `CPS` does nothing else.
757There is no recursive unwrapping of "thenables" nor other promises as with
758the Promise constructor.
759
760In addition, the CPS object `CPS(cpsFunction)` retains the same information
761by delivering the same functionality via direct funtion calls with the same callbacks!
762That is, the following calls are identical:
763```js
764cpsFunction(cb1, cb2, ...)
765CPS(cpsFunction)(cb1, cb2, ...)
766```
767That means, the wrapped CPS function can be dropped directly into the same code
768preserving all the functionality with no change!
769
770In regard of composing asynchronous calls, with CPS functions it can be as simple as in
771the above example.
772
773
774## Error handling
775> Error handling: As we shall see later, error handling is simpler with Promises, because, once again, there isn’t an inversion of control. Furthermore, both exceptions and asynchronous errors are managed the same way.
776
777In regards of error handling,
778the following paragraph in here http://exploringjs.com/es6/ch_promises.html#_chaining-and-errors
779seems relevant:
780
781> There can be one or more then() method calls that don’t have error handlers. Then the error is passed on until there is an error handler.
782```js
783asyncFunc1()
784.then(asyncFunc2)
785.then(asyncFunc3)
786.catch(function (reason) {
787 // Something went wrong above
788});
789```
790
791And here is the same example with CPS functions:
792```js
793CPS(cpsFunc1)
794.chain(cpsFunc2)
795.chain(cpsFunc3)
796.map(null, reason => {
797 // Something went wrong above
798});
799```
800Here the `map` method is used with two arguments
801and the second callback considered as holding errors,
802in the same way as the Promises achieve that effect.
803
804There is, however, no a priori restriction for the error callback
805to be the second argument, it can also be the first callback
806as in [Fluture](https://github.com/fluture-js/Fluture) or Folktale's [`Data.Task`](https://github.com/folktale/data.task), or the last one, or anywhere inbetween.
807
808Similar to Promises, also for CPS functions, handling
809both exceptions and asynchronous errors can be managed the same uniform way.
810Or the multiple callbacks feature of CPS functions can be utilized
811to handle errors of different nature in different callbacks,
812such as for instance [Fun-Task does](https://github.com/rpominov/fun-task/blob/master/docs/exceptions.md).
813
814On the other hand, in contrast to Promises,
815the CPS functions allow for clean separation between exceptions such as bugs
816that need to be caught as early as possible, and asynchronous errors
817that are expected and returned via the error callbacks calls.
818The absence of similar feature for Promises attracted [considerable criticisms](https://medium.com/@avaq/broken-promises-2ae92780f33).
819
820
821## Functional signatures
822> Cleaner signatures: With callbacks, the parameters of a function are mixed; some are input for the function, others are responsible for delivering its output. With Promises, function signatures become cleaner; all parameters are input.
823
824The "curried nature" of the (parametrized) CPS functions
825ensures clean separation between their input parameters
826and the callbacks that are used to hold the output only:
827```js
828const paramCps = (param1, param2, ...) => (cb1, cb2, ...) => { ... }
829```
830Here the output holding callbacks `cb1, cb2, ...` are
831cleanly "curried away" from the input parameters `param1, param2, ...`.
832
833Note that, without currying, it would not be possible to achieve similar separation.
834If function is called directly without currying, it is impossible to tell
835which arguments are meant for input and which for output.
836
837The principle here is very analogous to how that separation is achieved by Promises,
838except that the CPS function do not impose any restricitons on the
839number of their callback calls, nor the number of arguments passed to each callback
840with each call.
841
842
843## Standardization
844> Standardized: Prior to Promises, there were several incompatible ways of handling asynchronous results (Node.js callbacks, XMLHttpRequest, IndexedDB, etc.). With Promises, there is a clearly defined standard: ECMAScript 6. ES6 follows the standard Promises/A+ [1]. Since ES6, an increasing number of APIs is based on Promises.
845
846The CPS functions build directly on the standard already established for JavaScript functions.
847The provided methods such as `of` (aka `pure`, `return`), `map` (aka `fmap`), `chain` (aka `flatMap`, `bind`) strictly follow the general standards for algebraic data types established by Functional Programming languages and Category Theory.
848
849
850
851
852# Functional and Fluent API
853
854The `CPS` function transforms any CPS function into that very same CPS function,
855to which in addition all API methods can be applied.
856The same methods are provided on the `CPS` namespace and
857can be applied directly to CPS functions with the same effect.
858For instance, the following expressions are equivalent ([in the sense of fantasyland](https://github.com/fantasyland/fantasy-land#terminology)):
859```js
860CPS(cpsFun).map(f)
861map(f)(cpsFun)
862map(f, cpsFun)
863```
864Note that the functional style let us simply drop in CPS functions as plain functions,
865whereas to use `map` as method we need to wrap them into `CPS()` first.
866
867And the equivalent multiple argument versions are:
868```js
869CPS(cpsFun).map(f1, f2, ...)
870map(f1, f2, ...)(cpsFun)
871map(f1, f2, ..., cpsFun)
872```
873In the last expression, only the last argument is a CPS function,
874whereas all other arguments are arbitrary functions
875acting by means of their return values.
876
877
878## Conventions
879In the following we slightly abuse the notation by placing methods directly
880on the CPS functions, where the meaning is always after wrapping the functions into `CPS`.
881That is, we write
882```js
883cpsFun.map(f)
884// instead of
885CPS(cpsFun).map(f)
886```
887We could have used instead the equivalent functional style `map(f)(cpsFun)`,
888but the fluent style seems more common in JavaScript and closer to how Promises are used,
889so we use it instead.
890
891
892## CPS.map
893The `map` method and the equivalent `CPS.map` function in their simplest form
894are similar to `Array.map` as well as other `map` functions/methods used in JavaScript.
895
896### Mapping over single function
897In the simplest case of a single function `x => f(x)` with one argument,
898the corresponding transformation of the CPS function only affects the first callback,
899very similar to how the function inside `.then` method of a Promise only affects the fulfilled value:
900```js
901const newPromise = oldPromise.then(f)
902```
903
904Except that the `map` behavior is simpler with no complex promise recognition nor any thenable unwrapping:
905```js
906const newCps = CPS(oldCps).map(f)
907// or equivalently in point-free functional style
908const newCps = map(f)(oldCps)
909// or equivalently using pipeline
910const newCps = pipeline(oldCps)(map(f))
911```
912The `newCps` function will call its first callback
913with the single transformed value `f(res)`,
914whereas the functionality of the other callbacks remains unchanged.
915
916Also the return value of CPS function always remains unchanged after transforming with any `map` invocation, e.g. `newCps` above returns the same value as `oldCps`.
917
918
919The last two expressions have the advantage that no wrapping into `CPS()` is needed. The `pipeline` version in addition corresponds to the natural flow - get `oldCps` first, then pass to the transformer. This advantage appears even more visible with anonymous functions:
920```js
921// outputting 2-tuple of values
922const newCps = pipeline(x => cb => cb(x+1, x+2))(
923 // the 2-tuple is passed as args to function inside `map`
924 map((val1, val2) => val1 * val2)
925)
926// or equivalently using the `.map` method via CPS wrapper
927const newCps = CPS(x => cb => cb(x+1, x+2))
928 .map((val1, val2) => val1 * val2)
929// to compare with point-free style
930const newCps = map((val1, val2) => val1 * val2)(
931 x => cb => cb(x+1, x+2)
932)
933```
934
935
936### Mapping over multiple functions
937
938As `map(f)` is itself a function, its JavaScript signature provides us with the power to pass to it arbitrary number of arguments: `map(f1, f2, ...)`. This added power appear very handy for CPS functions with multiple outputs via multiple callbacks, where we can apply the `n`th function `fn` to transform the output of the `n`th callback:
939```js
940const newCps = CPS(oldCps).map(res => f(res), err => g(err))
941// or simply
942const newCps = CPS(oldCps).map(f, g)
943// or equivalently in point-free style
944const newCps = map(f, g)(oldCps)
945// or with pipeline
946const newCps = pipeline(oldCps)(map(f,g))
947```
948
949Here we are calling the second result `err` in analogy with promises,
950however, in general, it can be any callback without extra meaning.
951The resulting CPS function will call its first and second callbacks
952with correspondingly transformed arguments `f(res)` and `g(res)`,
953whereas all other callbacks will be passed from `newCps` to `oldCps` unchanged.
954
955The latter property generalizes the praised feature of Promises,
956where a single error handler can deal with all accumulated errors.
957In our case, the same behavior occurs for the `n`th callback
958that will be picked by only those `map` invocations holding functions at their `n`th spot.
959For instance, a possible third callback `progress`
960will similaly be handled only invocations of `map(f1, f2, f3)`
961with some `f3` provided.
962
963
964### Map taking arbitrarily many functions with arbitrary numbers of arguments
965In most general case, `map` applies its argument functions to several arguments passed at once to corresponding callbacks:
966```js
967const oldCps = x => (cb1, cb2, cb3) => {
968 cb1(vals1); cb2(vals2); cb3(vals3)
969}
970// now vals1 are tranformed with f1, vals2 with f2, vals3 with f3
971const newCps = CPS(oldCps).map(f1, f2, f3)
972```
973
974That means, the pattern can be generalized to
975```js
976const newCps = CPS(oldCps).map((res1, res2, ...) => f(res1, res2, ...))
977```
978or passing arbitrary number of arguments with [rest parameters syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters):
979```js
980const newCps = CPS(oldCps).map((...args) => f(...args))
981// which is the same as
982const newCps = CPS(oldCps).map(f)
983```
984or passing only some of the arguments:
985```js
986const newCps = CPS(oldCps)
987 .map((iAmThrownAway, ...rest) => f(...rest))
988```
989or picking props from multiple objects selectively via destructuring:
990```js
991const newCps = CPS(oldCps)
992 // select only important props and transform as 2-tuple
993 .map(({name: name1}, {name: name2}) => f(name1, name2))
994```
995None of these transformations would be as convenient with Promises where only single values are ever being passed.
996
997
998### Functor laws
999The `map` method for single functions of single argument satisfies the functor laws.
1000That is, the following pairs of expressions are equivalent:
1001```js
1002cpsFun.map(f).map(g)
1003// and
1004cpsFun.map(x => g(f(x)))
1005```
1006as well as
1007```js
1008cpsFun
1009// and
1010cpsFun.map(x => x)
1011```
1012
1013In fact, we have more general equivalences with multiple arguments:
1014```js
1015cpsFun.map(f1, f2, ...).map(g1, g2, ...)
1016// and
1017cpsFun.map(x1 => g1(f1(x1)), x2 => g2(f2(x2)), ...)
1018```
1019where in addition, the number of `f`'s can differ from the number of `g`'s,
1020in which case the missing maps are replaced by the identities.
1021
1022
1023### CPS.of
1024The static method `CPS.of` that we simply call `of` here
1025is the simplest way to wrap values into a CPS function:
1026```js
1027const of = (x1, x2, ...) => callback => callback(x1, x2, ...)
1028```
1029or equivalently
1030```js
1031const of = (...args) => callback => callback(...args)
1032```
1033
1034Here the full tuple `(x1, x2, ...)` becomes a single output of
1035the created CPS function `of(x1, x2, ...)`.
1036
1037As mentioned before,
1038`of` and `map` for single functions with single argument
1039conform to the [Pointed Functor](https://stackoverflow.com/questions/39179830/how-to-use-pointed-functor-properly/41816326#41816326),
1040that is the following expressions are equivalent:
1041```js
1042of(x).map(f)
1043of(f(x))
1044// both expressions are equivalent to
1045cb => cb(f(x))
1046```
1047In our case, the first expression maps `f` over the CPS function `cb => cb(x)` by transforming its single output `x`,
1048whereas the second one outputs `f(x)` direclty into its callback, which is obviously the same.
1049
1050More generally, the following are still equivalent
1051with the same reasoning:
1052```js
1053of(x1, x2, ...).map(f)
1054// and
1055of(f(x1, x2, ...))
1056// are equivalent to
1057cb => cb(f(x1, x2, ...))
1058```
1059
1060
1061## CPS.chain
1062
1063### Transforming multiple arguments into multiple arguments
1064There is a certain lack of symmetry with the `map` method,
1065due to the way functions are called with several arguments but
1066only ever return a single value.
1067
1068But what if we want not only to consume, but also to pass multiple arguments to the callback of the new CPS function?
1069
1070No problem. Except that, we should wrap these into another CPS function and use `chain` instead:
1071```js
1072const newCps = CPS(oldCps)
1073 .chain((x1, x2, ...) => of(x1 + 1, x2 * 2))
1074// or explicitly
1075const newCps = CPS(oldCps)
1076 .chain((x1, x2, ...) => cb => cb(x1 + 1, x2 * 2))
1077```
1078
1079Here we pass both `x1 + 1` and `x2 * 2` simultaneously into the callback `cb`.
1080Generalizing Promises that only hold one value, we can regard `(x1, x2, ...)` as tuple of values held inside single CPS function,
1081in fact, all being passed to only its first callback.
1082Now the output values of `oldCps` are passed to the functions inside
1083 `chain`, get transformed it according to the second CPS function,
1084i.e. into the pair `(x1 + 1, x2 * 2)`,
1085and finally passed to the first callback of `newCps`.
1086The final result is exactly the intended one, that is,
1087the result tuple output `(x1, x2, ...)` from `oldCps`'s first callback is transformed into the new pair `(x1 + 1, x2 * 2)`
1088that becomes the output of `newCps`.
1089
1090
1091### Why is it called `chain`?
1092Have you noticed the difference between how `map` and `chain` are used?
1093Here is the simplest case comparison:
1094```js
1095cpsFun.map(x => x+1)
1096cpsFun.chain(x => of(x+1))
1097```
1098In the first expression the value `x+1` is passed directly,
1099in the second it is wrapped into CPS function with `of`.
1100The first time the return value of the function inside `map` is used,
1101the second time it is the output value of the CPS function inside `chain`.
1102
1103The "Promised" way is very similar:
1104```js
1105promise.then(x => x+1)
1106promise.then(x => Promise.resolve(x+1))
1107```
1108Except that both times `then` is used,
1109so we don't have to choose between `map` and `chain`.
1110However, such simplicity comes with its cost.
1111Since `then` has to do its work to detect
1112and recursively unwrap any Promise or in fact any ["thenable"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve),
1113there can be loss in performance as well as
1114in safety to refactor
1115```js
1116promise.then(f).then(g)
1117```
1118to
1119```js
1120promise.then(x => g(f(x)))
1121```
1122which is [not always the same](https://stackoverflow.com/a/50173415/1614973).
1123
1124On the other hand, our `map` method
1125conforms to the Functor composition law,
1126that is the following expressions are always equivalent
1127and safe to refactor to each other (as mentioned above):
1128```js
1129cpsFun.map(f).map(g)
1130// and
1131cpsFun.map(x => g(f(x)))
1132```
1133And since now no other work is involved,
1134performance wins.
1135
1136However, if we try use `map` in the second case,
1137instead of `chain`, we get
1138```js
1139cpsFun.map(x => of(x+1))
1140```
1141which emits `of(x+1)` as output, rather than `x+1`.
1142That is, the output result of our CPS function is another CPS function,
1143so we get our value wrapped twice.
1144This is where `chain` becomes useful,
1145in that in removes one of the layers,
1146aka "flattens" the result,
1147which is why it is also called `flatMap`.
1148
1149For CPS functions, the name `chain` is particularly descriptive
1150because it effectively chains two such functions
1151by passing the output of one funciton as input to the other.
1152And the rule becomes very simple:
1153
1154*Use `map` with "plain" functions and `flatMap` with CPS functions inside*
1155
1156
1157### Composing multiple outputs
1158In the previous case we had `chain` over a CPS function with single output,
1159even when the output itself is a tuple.
1160In comparison, a general Promise has two outputs - the result and the error.
1161Of course, in case of Promises, there are more restrctions such as only one of these two
1162outputs can be emitted.
1163
1164No such restrictions are iposed on CPS functions,
1165where two or more callbacks can receive arbitrary number of outputs arbitrarily often.
1166To keep things simple, consider how the Promise functionality can be extended
1167without the output exclusivity restriction:
1168```js
1169const cpsFun = (cb1, cb2) => {
1170 /* some work here... */
1171 cb1(res1)
1172 /* some more work... */
1173 cb2(res2)
1174}
1175```
1176So both callbacks are called with their individual results at different moments.
1177
1178A very useful and realistic example of this functionality would be,
1179when the server sent an error but then eventually managed to deliver the data.
1180That would be impossible to implement with Promises.
1181
1182Back to our example,
1183we now want to use the output for the next CPS computation:
1184```js
1185const newCps = cpsFun.chain(res => anotherCps(res))
1186```
1187We are now chaining aka sequentially executing `cpsFun`,
1188followed by the next parametrized CPS function
1189`anotherCps` applied to the result `res` of the previous computation.
1190
1191So how should we combine both computations?
1192And should we apply the second one to `res1` or `res2`?
1193
1194If you read the above description of the `map`, you know the answer.
1195The principle is the same. As we only pass one function to `chain`,
1196only the first callback is affected.
1197That is, we must pass only the first result `res1` to `anotherCps`.
1198Whose output will be our final result inside the first callback,
1199whereas all other callbacks remain unchanged.
1200
1201So the functionality of `newCps` is equivalent to the following:
1202```js
1203const newCps = (...args) => cpsFun(
1204 res1 => anotherCps(res1)(...args),
1205 res2 => cb2(args[1])
1206)
1207```
1208Note how all callbacks are passed to the inner CPS function in the same order as `args`.
1209That guarantees that no outgoing information from `anotherCps` can ever get lost.
1210
1211
1212### Passing multiple CPS functions to `chain`
1213
1214Similarly to `map`, also `chain` accepts arbitrary number of functins,
1215this time CPS functions:
1216```js
1217const newCps = oldCps.chain(
1218 res1 => anotherCps1(res1),
1219 res2 => anotherCps2(res2),
1220 ...
1221)
1222```
1223Look how similar it is with Promises usage:
1224```js
1225// Promises
1226promise.then(
1227 res => anotherPromise(res),
1228 error => errorHandlerPromise(err)
1229)
1230// CPS functions
1231cpsFun.chain(
1232 res => anotherCps(res),
1233 err => errorHandlerCps(err)
1234)
1235```
1236You can barely tell the difference, can you?
1237
1238Or, since the CPS functions are just functions,
1239we can even drop any Promise `then` function directly into `chain`:
1240```js
1241// CPS functions
1242cpsFun.chain(
1243 res => anotherPromise.then(res),
1244 error => errorHandlerPromise.then(err)
1245)
1246// or just the shorter
1247cpsFun.chain( anotherPromise.then, errorHandlerPromise.then )
1248```
1249
1250On the other hand, the CPS functions are more powerful
1251in that they can call their callbacks multiple times in the future,
1252with the potential of passing further important information back to the caller.
1253Also we don't want to prescribe in which callback the error should go,
1254treating all the callbacks in a uniform way.
1255Here is some pseudocode demonstrating general usage of `chain`
1256with mulitple functions:
1257```js
1258const newCps = oldCps.chain(
1259 (x1, x2, ...) => cpsFun1(x1, x2, ...),
1260 (y1, y2, ...) => cpsFun2(y1, y2, ...),
1261 ...
1262)
1263```
1264And the functionality of `newCps` is equivalent to
1265```js
1266const newCps = (cb1, cb2, ...) => oldCps(
1267 (x1, x2, ...) => cpsFun1(x1, x2, ...)(cb1, cb2, ...)
1268 (y1, y2, ...) => cpsFun1(y1, y2, ...)(cb1, cb2, ...)
1269 ...
1270)
1271```
1272As any other CPS function, our `newCps` accepts arbitrary number of callbacks
1273that are simply all passed to each interior CPS function in the same order.
1274That leads to the effect of capturing output events from each of them,
1275and "flattening" the event streams via simple merging.
1276
1277
1278### Monadic laws
1279When used with single callback argument,
1280`of` and `chain` satisfy the regular monadic laws.
1281
1282#### Associativity law
1283The associativity law analogue for Promises would be the equivalence of
1284```js
1285promise
1286 .then(x1 => promise1(x1))
1287 .then(x2 => promise2(x2))
1288// and
1289promise
1290 .then(x1 => promise1.then(x2 => promise2(x2)))
1291```
1292which [are, however, not always equivalent](https://stackoverflow.com/a/50173415/1614973).
1293
1294
1295For CPS functions in contrast, we do indeed obtain true equivalence of
1296```js
1297cpsFun
1298 .chain(x1 => cpsFun1(x1))
1299 .chain(x2 => cpsFun2(x2))
1300// and
1301cpsFun
1302 .chain(x1 => cpsFun1.chain(x2 => cpsFun2(x2)))
1303```
1304Because, since these are just functions,
1305both expressions can be direclty expanded into
1306```js
1307cb => cpsFun(
1308 x1 => cpsFun1(x1)(
1309 x2 => cpsFun2(x2)(cb)
1310 )
1311)
1312```
1313That is, the output `x1` of `cpsFun` is passed to `cpsFun1`,
1314which transforms it into `x2` as output,
1315subsequently passed to `cpsFun2`,
1316whose output is finally diverted direclty into `cb`.
1317
1318More generally, similar law still holds for mulitple arguments,
1319that is the following are equivalent
1320```js
1321cpsFun
1322 .chain(f1, f2, ...)
1323 .chain(g1, g2, ...)
1324// and
1325cpsFun
1326 .chain(
1327 (...xs) => f1(...xs).chain((...ys) => g1(...ys)),
1328 (...xs) => f2(...xs).chain((...ys) => g2(...ys)),
1329 ...
1330 )
1331```
1332as both expand into
1333```js
1334(...cbs) => cpsFun(
1335 (...xs) => f1(...xs)((...ys) => g1(...ys)(...cbs)),
1336 (...xs) => f2(...xs)((...ys) => g2(...ys)(...cbs)),
1337 ...
1338)
1339```
1340
1341#### Identity laws
1342The monadic identity laws asserts that both following
1343expressions are equivalent to the CPS function `cpsFun`:
1344```js
1345cpsFun
1346// and
1347chain(y => of(y))(cpsFun)
1348```
1349Here `cpsFun` is any CPS function,
1350whose output is composed with the CPS identity `y => of(y)`.
1351
1352
1353On the other hand, taking a parametrized CPS function
1354`x => cpsFun(x)` and moving the identity to other side,
1355we get the other law asserting the equivalence of:
1356```js
1357x => cpsF(x)
1358// is equivalent to
1359x => chain(
1360 y => cpsF(y)
1361)(
1362 cb => cb(x)
1363)
1364```
1365
1366Once expanded, both equivalences are
1367became straightforward to check.
1368More interestingly, they still hold for multiple arguments:
1369```js
1370cpsFun
1371// is equivalent to
1372cpsFun.chain(
1373 (...ys) => of(...ys),
1374 (...ys) => of(...ys),
1375 ... /* any number of identities */
1376)
1377```
1378and the other way around:
1379```js
1380(...xs) => cpsF(...xs)
1381// is equivalent to
1382(...xs) => chain(
1383 (...ys) => cpsF(...ys))((...cbs) => cbs.map(cb => cb(...xs))
1384)
1385```
1386
1387
1388## Application of `chain`: Turn Node API into Promise style callbacks
1389The Node style callbacks with error argument first
1390force their errors to be handled each single time:
1391```js
1392someNodeFunction(param, callback((error, result) => {
1393 if (error) mustHandle...
1394 doMoreWork(result, callback((error, result) => {
1395 ...
1396 }))
1397}))
1398```
1399In constrast, Promises make it possible to handle all errors with one callback at the end:
1400```js
1401promise
1402 .then(doSomeWork)
1403 .then(doMoreWork)
1404 ...
1405 .catch(handleAllErrors)
1406```
1407
1408Many libraries offering methods to "promisify" Node style callbacks.
1409The trouble is the [Gorilla-Banana Problem](https://www.johndcook.com/blog/2011/07/19/you-wanted-banana/): Promises added a lot of other functionality and limitations that not everyone needs.
1410For instance, it is perfectly legal to call callbacks muiltiple times
1411(for which there are many use cases such as streams and event handlers),
1412whereas the "promisification" would only see the first call.
1413
1414On the other hand, we can curry any callback-last Node method into CPS function
1415```js
1416const cpsErrback = (...args) => cb => nodeApi(...args, cb)
1417```
1418and subsequently `chain` it into "Promise" style CPS function
1419with the same pair of callbacks, except that no other functionality is added nor removed:
1420```js
1421const promiseStyle = CPS(cpsErrback)
1422 .chain((error, ...results) => (resBack, errBack) => error
1423 ? errBack(error)
1424 : resBack(...results)
1425 )
1426```
1427
1428Now we can chain these CPS funcitons exactly like Promises,
1429passing only the first callback, and handle all errors at the end in the second callback.
1430```js
1431promiseStyle
1432 .chain(doSomeWork)
1433 .map(doMoreWork)
1434 ...
1435 .chain(null, handleAllErrors)
1436
1437```
1438
1439
1440
1441## CPS.ap (TODO)
1442The `ap` operator plays an important role when
1443*running functions in parallel* and combining their outputs.
1444
1445### Running CPS functions in parallel
1446Similarly to `map(f)` applying a plain function `f` to (the output of) a CPS function,
1447`ap(cpsF)` applies functions that are themselves outputs of some CPS function,
1448delivered via callbacks.
1449A simple example is getting result from a database query via `cpsDb` function
1450and display it with via function transformer obtained from an independent query:
1451```js
1452// returns result via 'cb(result)' call
1453const cpsDb = query => cb => getQuery(query, cb)
1454// returns transformer function via 'cb(transformer)'
1455const cpsTransformer = path => cb => getTransformer(path, cb)
1456// Now use the 'ap' operator to apply the transformer to the query result:
1457const getTransformedRes = (query, path) => CPS(cpsDb(query)).ap(cpsTransformer(path))
1458// or equivalently in the functional style, without the need of the 'CPS' wrapper:
1459const getTransformedRes = (query, path) => ap(cpsTransformer(path))(cpsDb(query))
1460```
1461
1462Note that we could have used `map` and `chain` to run the same functions sequentially,
1463one after another:
1464```js
1465(query, path) => CPS(cpsDb(query))
1466 .chain(result => cpsTransformer(path)
1467 .map(transformer => transformer(result)))
1468```
1469Here we have to nest, in order to keep `result` in the scope of the second function.
1470However, `result` from the first function was not needed to run the `cpsTransformer`,
1471so it was a waste of time and resources to wait for the query `result`
1472before getting the `transformer`.
1473It would be more efficient to run both functions in parallel and then combine the results,
1474which is precisely what the `ap` operator does.
1475
1476
1477### Lifting functions of multiple parameters
1478Perhaps the most important use of the `ap` operator is lifting plain functions
1479to act on results of CPS functional computations.
1480That way simple plain functions can be created and re-used
1481with arbitrary data, regardless of how the data are retrieved.
1482In the above example we have used the general purpose plain function
1483```js
1484const f =(result, transformer) => transformer(result)
1485```
1486which is, of course, just the ordinary function call.
1487That function was "lifted" to act on the data delivered as outputs
1488from separate CPS functions.
1489
1490Since this use case is very common,
1491we have the convenience operator doing exactly that called `lift`:
1492```js
1493const getTransformedRes = (query, path) =>
1494 lift(transformer => transformer(result))
1495 (cpsDb(query), cpsTransformer(path))
1496```
1497
1498#### Promise.all
1499The common way to run Promises in parallel via `Promise.all`
1500is a special case of the `lift` usage,
1501corresponding to lifting the function
1502combining values in array:
1503```js
1504const combine = (...args) => args
1505Promise.all = promiseArray => lift(combine)(...promiseArray)
1506```
1507
1508Similarly, the same `combine` function (or any other) can be lifted
1509over to act on outputs of CPS functions:
1510```js
1511(cpsF1, cpsF2, ...) => lift((x1, x2, ...) => f(x1, x2, ...))(cpsF1, cpsF2, ...)
1512```
1513
1514
1515#### Usage notes
1516Note that `lift` (and `ap`) are best used when
1517their arguments can only be retrieved as outputs from separate CPS functions.
1518If for instance, both `result` and `transformer`
1519can be delivered via single query,
1520using `lift` would be a waste of its parallel execution functionality.
1521Instead we could have used the simple `map` with a single CPS function:
1522```js
1523const getTransformedResSingle = (query, path) =>
1524 CPS(getData(query, path)).map((result, transformer) => transformer(result))
1525```
1526Note how the `map` function is applied with two arguments,
1527which assumes the `getData` function to have these in a single callback output
1528as `callback(result, transformer)`.
1529
1530
1531### Applying multiple functions inside `ap`
1532
1533As with `map` and `chain`, the same rules apply for `ap`:
1534```js
1535const transformed = CPS(cpsFun).ap(F1, F2, ...)
1536```
1537When called with callbacks `(cb1, cb2, ...)`,
1538the output from `cb1` is transformed with the output function from `F1`,
1539the output from `cb2` with function from `F2` and so on.
1540
1541For instance, a CPS function with two callbacks such as `(resBack, errBack)`
1542can be `ap`ed over a pair of CPS functions, outputting plain functions each
1543```js
1544// These call some remote API
1545const cpsResTransformer = cb => getResTransformer(cb)
1546const cpsErrHandler = cb => getErrHandler(cb)
1547// This requires error handlers
1548const cpsValue = (resBack, errBack) => getValue(resBack, errBack)
1549// Now run all requests in parallel and consume both outputs as they arrive
1550// via plain function call
1551CPS(cpsValue).ap(cpsResTransformer, cpsErrHandler)(
1552 res => console.log("Transformed Result: ", res)
1553 err => console.log("The Error had been handled: ", err)
1554)
1555```
1556
1557The above pattern can be very powerful,
1558for instance the `cpsErrHandler` function
1559can include a remote retry or cleanup service
1560that is now completely abstracted away from the main code pipeline!
1561
1562
1563
1564### Applicative laws
1565The `ap` operator together with `of` conforms to the [Applicative interface](https://github.com/rpominov/static-land/blob/master/docs/spec.md#applicative)
1566
1567
1568
1569## CPS.merge (TODO)
1570The `merge` operator merges outputs events from multiple CPS functions,
1571which occurs separately for each callback slot:
1572```js
1573// merge outputs into single CPS function
1574const cpsMerged = merge(cpsF1, cpsF2, ...)
1575// cb1 receives all outputs from the first callback of each of the cpsF1, cpsF2, ...
1576cpsMerged(cb1, cb2, ...)
1577```
1578Here the `N`-th callback of `cpsMerged` gets called each time
1579the `N`-th callback of any of the functions `cpsF1`, `cpsF2`, ...,
1580with the same arguments.
1581This behaviour corresponds to merging the values emitted by
1582each event stream.
1583
1584### Relation with Promise.race
1585The `merge` operator generalizes the functionality provided for Promises via `Promise.race`.
1586Since Promises only take the first emitted value from each output,
1587merging those results in the earliest value from all being picked by the Promise,
1588hence the direct analogy with `Promise.race`.
1589
1590### Commutative Monoid
1591The `merge` operator makes the set of all CPS functions a commutative Monoid,
1592where the identity is played by the trivial CPS function that never emits any output.
1593
1594
1595
1596
1597## CPS.filter
1598The `filter` operator does the obvious thing,
1599that is trasform one CPS function into another by filtering the output.
1600As the output may have several arguments,
1601the filter function is also variadic:
1602```js
1603const cpsFiltered = filter(pred)(cpsFun)
1604```
1605Here `pred` is a Boolean function called for each output tuple.
1606The resulting `cpsFiltered` emits only the output for which `pred` returns `true`.
1607
1608
1609### Filtering over multiple functions
1610Consistently with other operators,
1611also `filter` accepts multiple predicate functions,
1612each matched against the ouput from the corresponding callback.
1613
1614That is, the filtered function
1615```js
1616const cpsFiltered = filter(p1, p2, ...)(cpsFun)
1617```
1618when passed callbacks `(cb1, cb2, ...)`,
1619calls `cb1` with the same output `(x1, x2, ...)` as `cpsFun` does,
1620as long as `p1(x1, x2, ...)` returns `true`, otherwise the call is skipped.
1621Similarly, `p2` filters the output of `cb2` and so on.
1622The callbacks not corresponding to any predicate function
1623will be unaffected and the predicates corresponding to no callbacks
1624are ignored.
1625
1626
1627### Implementation via `chain`
1628Filtering is really chaining:
1629```js
1630// pass through only input truthy `pred`
1631const cpsFilter = pred => (...input) => cb => {
1632 if (pred(...input)) cb(...input)
1633}
1634// now chain with `cpsFilter(pred)`:
1635const filter = pred => CPS(cpsFun)
1636 .chain(cpsFilter(pred))
1637```
1638And the variadic version reuses the same `cpsFilter` applied to each predicate:
1639```js
1640// call `chain` with the list of arguments, one per each predicate
1641const filter = (...pred) => CPS(cpsFun)
1642 .chain(...pred.map(cpsFilter))
1643// or using the `pipeline` operator
1644const filter = (...pred) => pipeline(cpsFun)(
1645 chain(...pred.map(cpsFilter))
1646)
1647```
1648
1649
1650
1651
1652## CPS.scan
1653The `scan` operator acts as "partial reduce" for each output.
1654Important example is the stream of states affected by stream of actions:
1655```js
1656const cpsState = scan(f)(initState)(cpsAction)
1657```
1658Here `f` is the reducing function accepting current `state` and
1659the next `action` and returning the next state as `f(state, action)`,
1660that is the signature is
1661```js
1662f :: (state, action) -> state
1663```
1664
1665Similarly to `filter`, `scan` can also be derived from `chain`:
1666```js
1667const scan = f => state => cpsAction => pipeline(cpsAction)(
1668 // action may contain several arguments
1669 chain((...action) => cb => {
1670 state = f(state, ...action)
1671 cb(state)
1672 })
1673)
1674```
1675Note that the function inside `chain` updates the `state` outside its scope,
1676so it is not pure, however, we can still `chain` it like any other function.
1677
1678And here is the mulitple arguments generalization:
1679```js
1680// `reducers` and `states` are matched together by index
1681const scan = (...reducers) => (...states) => {
1682 cpsAction => pipeline(cpsAction)(
1683 // chaining with multiple reducers, one per state
1684 chain(...states.map((, idx) => cb => {
1685 // accessing states and reducers by index
1686 cb( states[idx] = reducers[idx](states[idx], ...action) )
1687 })
1688 ))
1689}
1690```