├── .circleci └── config.yml ├── .gitignore ├── .travis.yml ├── DOCUMENTATION.md ├── LICENSE ├── README.md ├── index.js ├── package-lock.json ├── package.json ├── renovate.json ├── test ├── chain.test.js ├── config.js ├── cps.test.js ├── filter.test.js ├── helpers │ ├── ava-patched.js │ ├── ava-patched.test.js │ ├── tape-patched.js │ └── tape-patched.test.js ├── map.test.js ├── of.test.js ├── pipeline.test.js ├── preserve-state.test.js └── scan.test.js └── toc /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:10 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/repo 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | - v1-dependencies-{{ checksum "package.json" }} 26 | # fallback to using the latest cache if no exact match is found 27 | - v1-dependencies- 28 | 29 | - run: yarn install 30 | 31 | - save_cache: 32 | paths: 33 | - node_modules 34 | key: v1-dependencies-{{ checksum "package.json" }} 35 | 36 | # run tests! 37 | - run: yarn test 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | npm-debug.log* 3 | yarn-debug.log* 4 | yarn-error.log* 5 | 6 | # Dependency directories 7 | node_modules/ 8 | 9 | # Coverage 10 | .nyc_output 11 | .codecov 12 | coverage.lcov 13 | coverage/ 14 | 15 | # Apple garbage 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | - '10' 5 | - '8' 6 | after_success: 7 | npm run cov 8 | npm run codecov -------------------------------------------------------------------------------- /DOCUMENTATION.md: -------------------------------------------------------------------------------- 1 | # Tiny-Cps 2 | Tiny 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 12 | const getServerStuff = callback => ajaxCall(json => callback(json)) 13 | // enlightened 14 | const 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? 93 | Functions are the most basic and powerful concept. 94 | A whole program can be written as funciton, 95 | taking input data and producing output. 96 | However, viewing function's return value as the only output is often too limited. 97 | For instance, all asynchronous Node API methods rely on the output data 98 | returned via callbacks rather than via functions' return values. 99 | This 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 104 | The 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) 105 | advocated to "reduce the code obesity" by building generic hierarchical ways of composing entire programs. 106 | The present proposal attempts to provide some way of how such composability can be achieved. 107 | 108 | 109 | ## What is new here? 110 | Traditionally Continuation-Passing Style is implemented 111 | via callbacks as part of the function's parameters: 112 | ```js 113 | const api = (input, callback) => doSomeWork(input, callback) 114 | ``` 115 | A fundamental problem here is that the input and output data are getting mixed 116 | among function's parameters, making it hard to separate one from another. 117 | 118 | Our main proposal is to solve this problem via currying: 119 | ```js 120 | const api = input => callback => doSomeWork(input, callback) 121 | ``` 122 | Now the output is cleanly separated from the input via the function's curried signature. 123 | Further parameters can easily be added to the input: 124 | ```js 125 | const api = (input1, input2, ...) => callback => doSomeWork(input1, ..., callback) 126 | ``` 127 | as well as to the callbacks accepting output: 128 | ```js 129 | const api = (input1, input2, ...) => (callback1, callbacks2, ...) => 130 | doSomeWork(input1, ... , callback1, ...) 131 | ``` 132 | 133 | ### Variadic input and output 134 | JavaScript's functions are variadic by design, 135 | that is, are capable of accepting arbitrary number of arguments at the runtime. 136 | That feature makes it very convenient and powerful to implement optional parameters or set defaults: 137 | ```js 138 | const f = (required, optionalWithDefault = default, iAmOptional) => { ... } 139 | ``` 140 | Now, given the clean separation provided by currying as mentioned above, 141 | we get for free the full functional variadic power provded by JS: 142 | ```js 143 | const api = (...inputs) => (...callbacks) => doSomeWork(inputs, callbacks) 144 | ``` 145 | Here `...inputs` is the array holding all arguments passed to the function 146 | at the run time, by means of the [Rest parameters syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters). 147 | In particular, zero arguments are also allowed on each side. 148 | 149 | 150 | ### Full power of multiple outputs streams 151 | By its design, JavaScript's function can call 152 | any of its callbacks arbitrarily many times at arbitrary moments. 153 | This provides a simple implementation of multiple data streams 154 | emitted from a single function. 155 | Each stream value is passed as arguments of the callback, 156 | that is, a whole list of values can be emitted at the same time 157 | as arguments of the same function call. 158 | 159 | 160 | ### Functional progamming paradigm 161 | The proposed curried design rests on the well-known paradigms. 162 | It generalizes the [Kleisli arrows](https://en.wikipedia.org/wiki/Kleisli_category) 163 | `a -> m b` associated to the Monad `m`. 164 | In our case, the Continuation Monad `m b` corresponds to passing single `callback` function 165 | ```js 166 | const monad = callback => computation(callback) 167 | ``` 168 | and can be regarded as a "suspended computation". 169 | The 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. 171 | As part of the variadic functionality, we generalize these Monadic methods 172 | by allowing for arbitrary number of function arguments that are matched against the callbacks, see below. 173 | This allows for easy handling of multiple output streams with single methods. 174 | 175 | In addition to generalized Monadic methods dealing with sequential computations, 176 | generalized Applicative `ap` and derived `lift` are dealing with parallel ones. 177 | As well as Monoidal method `merge` dealing with merging multiple streams. 178 | See 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? 182 | The lazy vs eager functionality is already built in the function design: 183 | ```js 184 | const cpsFun = input => callback => doSomeWork(callback) 185 | // lazy - waiting to be called 186 | cpsFun(input) 187 | // eager - running with the callback passed 188 | cpsFun(input)(callback) 189 | ``` 190 | Both are of course just functions and function calls, and can be used depending on the need. 191 | 192 | 193 | ### Differences with Haskell 194 | Functional Programming in JavaScript has been largely influenced by Haskell. 195 | However, 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 | 199 | While 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 201 | var a = 0 202 | const f = x => { 203 | x = x + a 204 | } 205 | ``` 206 | that can be (pre-)composed with any other function `g`: 207 | ```js 208 | const g => y => y * 2 209 | const composed = y => f(g(x)) 210 | // or equivalently in functional way 211 | const compose = (f,g) => x => f(g(x)) 212 | const composed = compose(f,g) 213 | ``` 214 | The `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 | 220 | Again, 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 222 | const f1 = x => someComputation1(x) 223 | const f2 = y => someComputation2(y) 224 | const add = (a, b) => a + b 225 | // binary addition is (pre-)composed with both f1, f2 226 | const result = (x, y) => add(f1(x), f2(y)) 227 | ``` 228 | Defining such abstract composition operator is straightforward: 229 | ```js 230 | const binaryCompose => (h, f1, f2) => (x, y) => h(f1(x), f2(y)) 231 | const result = binaryCompose(add, f1, f2) 232 | ``` 233 | However, 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 235 | const binaryCompose1 => h => (f1, f2) => (x, y) => h(f1(x), f2(y)) 236 | const result = binaryCompose1(add)(f1, f2) 237 | ``` 238 | Now the inside functions `f1, f2` are visibly separated from the outside `h`. 239 | The 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 243 | The 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 248 | A *Continuation-Passing-Style (CPS) function* is any function 249 | ```js 250 | const cps = (f1, f2, ...) => { 251 | /* f1, f2, ... are called arbitrarily often with any number of arguments */ 252 | } 253 | ``` 254 | that expects to be called with zero or several functions as arguments. 255 | By *expects* we mean that this library and the following discussion 256 | only applies when functions are passed. 257 | In a strictly typed language that would mean those arguments are required to be functions. 258 | However, in JavaScript, where it is possible to pass any argument, 259 | we don't aim to force errors when some arguments passed are not functions 260 | and let the standard JavaScript engine deal with it the usual way, 261 | as per [garbage in, garbage out (GIGO)](https://en.wikipedia.org/wiki/Garbage_in,_garbage_out) principle. 262 | 263 | We also call the argument functions `f1, f2, ...` "callbacks" 264 | due to the way how they are used. 265 | Each of the callbacks can be called arbitrarily many times or never, 266 | with zero to many arguments each time. 267 | The number of arguments inside each callback 268 | can change from call to call and is even allowed to unlimitedly grow, 269 | e.g. `n`th call may involve passing `n` arguments. 270 | 271 | By a *parametrized CPS function* we mean any curried function with zero or more parameters 272 | that returns a CPS function: 273 | ```js 274 | const paramCps = (param1, param2, ...) => (f1, f2, ...) => { ... } 275 | ``` 276 | 277 | We shall adopt somewhat loose terminology calling *parametrized CPS functions* both 278 | the curried function `paramCps` and its return value `paramCps(params)`, 279 | in the hope that the context will make clear the precisce meaning. 280 | In the same vein, by a *function call* of the parametrized CPS function, 281 | we mean its call with both arguments and callbacks passed: 282 | ```js 283 | paramCps(...args)(...callbacks) 284 | ``` 285 | Otherwise `parmCps(...args)` is considered a *partial call*. 286 | 287 | 288 | ## Using CPS functions 289 | Using 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 293 | const 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 299 | cpsQuery({name: 'Jane'})( 300 | result => console.log("Your Query returned: ", result), 301 | error => console.error("Sorry, here is what happened: ", error) 302 | ) 303 | ``` 304 | The latter is very similar to how Promises are used: 305 | ```js 306 | promiseQuery({name: 'Jane'}).then( 307 | result => console.log("Your query returned: ", result), 308 | error => console.error("Sorry, an error happened: ", error) 309 | ) 310 | ``` 311 | Except that, calling `then` method is replaced by plain function call 312 | and arbitrary number of callbacks is allowed, 313 | each of which can be called arbitrary many times, 314 | as e.g. in the event streams. 315 | A Promise is essentially a CPS function with its first event cached, 316 | that can be implemented by chaining (via [`chain`](#cpschain), see below) any CPS function 317 | with the one picking and caching the first output from any callback. 318 | 319 | 320 | ## What about Callback Hell? 321 | There is an actual website called [*Callback Hell*](http://callbackhell.com/). 322 | The following callback hell example is shown: 323 | ```js 324 | fs.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 | 350 | The solution proposed there to avoid this "hell" consists of splitting into mulitple functions and giving names to each. 351 | However, naming is hard and 352 | [is not always recommended](https://www.cs.ucf.edu/~dcm/Teaching/COT4810-Fall%202012/Literature/Backus.pdf). 353 | 354 | 355 | Using CPS functions along with `map` and `chain` operators, 356 | we can break that code into a sequence of small functions, chained one after another 357 | without 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 361 | CPS(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 | 397 | Equivalently, 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 398 | in more functional (aka point-free) style: 399 | 400 | ```js 401 | pipeline( 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 | 411 | In the latter pattern there is no wrapper around the first CPS function, 412 | it is simply passed around through all the transformations in the sequence. 413 | 414 | Any such sequence of computations can be similaly achieved with just two operators - `map` and `chain`. 415 | In fact, just the single more powerful `chain` is enough, as e.g. the following are equivalent: 416 | 417 | ```js 418 | CPS(cpsFun).map((x, y) => f(x, y)) 419 | CPS(cpsFun).chain((x, y) => cb => cb(f(x, y))) 420 | ``` 421 | 422 | or, equivalently, using the `pipeline` operator 423 | 424 | ```js 425 | pipeline(cpsFun)( map((x, y) => f(x, y)) ) 426 | pipeline(cpsFun)( chain((x, y) => cb => cb(f(x, y)) ) 427 | ``` 428 | 429 | A limitation of the `chain` is its sequential nature. 430 | To run computations in parallel, the `ap` (aka `apply`) operator 431 | is more suitable, see below. 432 | 433 | 434 | ## Asynchronous iteration over array 435 | On of the functions in the above example illustrates 436 | how multiple outputs fit nicely in the asynchronous iteration pattern: 437 | 438 | ```js 439 | const 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 | 445 | Here we create the `jobCps` function that accepts callback 446 | and calls it repeatedly for each `file`. 447 | That wouldn't work with Promises that can only hold single value each, 448 | so you would need to create as many Promises as the number of elements in the `file` array. 449 | Instead, 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 456 | Any producer (aka executor) function 457 | 458 | ```js 459 | const producer = function(resolve, reject) { 460 | // some work ... 461 | if (everythingIsOk) resolve(result) 462 | else reject(error) 463 | } 464 | ``` 465 | 466 | as 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 | 468 | The constructed promise `new Promise(producer)` only keeps the very first call of either of the callbacks, 469 | whereas the producer function itself may call its callbacks multiple times, 470 | each of which would be fully retained as output when CPS functions are used instead of promises. 471 | 472 | ## Promises 473 | Any JavaScript Promise generates a CPS function via its `.then` method 474 | that completely captures the information held by the Promise: 475 | 476 | ```js 477 | const cpsFromPromise = (onFulfilled, onRejected) => 478 | promise.then(onFulfilled, onRejected) 479 | ``` 480 | 481 | The important restictions Promises arising that way are: 482 | 1. Among many callbacks passed, at most one is ever called. 483 | 2. Each of the callbacks is called precisely with one argument. 484 | CPS functions do not have such limitations. 485 | 486 | As any Promise provides a CPS function via its `then` method with two callbacks, 487 | it can be dropped direclty into any CPS operator: 488 | 489 | ```js 490 | CPS(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 | 497 | Here `(x, y)` is the first output from `cpsFun` (the one passed into the first callback). 498 | Now every such output will be passed into `somePromise` via [`chain`](#cpschain), 499 | that will subsequently pass its result or error into the final callbacks 500 | that are attached via plain function call. 501 | And even better, the error callbacks will also receive 502 | all error outputs from `cpsFun`, basically whatever is passed into its second callback. 503 | The outputs from both functions are simply merged together, 504 | due to the "flattening" job performed by the `chain`. 505 | 506 | Conversely, any CPS function, being just a function accepting callbacks as its arguments, 507 | can be dropped into the Promise constructor 508 | (from any Promise implementation) to return the Promise 509 | holding the first argument from the first output as its resolved value, 510 | while that from the second callback as error. 511 | 512 | 513 | ## Node API 514 | Any Node-Style function with one of more callbacks can be curried into a parametrized CPS function: 515 | 516 | ```js 517 | const readFileCPS = (path, options) => callback => fs.readFile(path, options, callback) 518 | ``` 519 | 520 | Here `readFileCPS` returns a CPS function for each values of its parameters 521 | `(path, options)`. 522 | 523 | Typically Node API callbacks are called with at least two arguments as 524 | `callback(error, arg1, ...)`, 525 | where the first argument is used as indication of error. 526 | CPS functions generalize this case to arbitrary number of callbacks 527 | accepting arbitrary number of arguments each. 528 | 529 | 530 | ## HTTP requests 531 | In a similar vein, any HTTP request with callback(s) can be regarded as parametrized CPS function: 532 | ```js 533 | const request = require('request') 534 | // the CPS function is just the curried version 535 | const requestCps = options => callback => http.request(options, callback) 536 | ``` 537 | 538 | Now `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 540 | const 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 | 548 | Or using the [native Node `https.request`](https://nodejs.org/api/https.html#https_https_request_options_callback): 549 | ```js 550 | const https = require('https') 551 | const httpsReqCps = (url, options) => cb => http.request(url, options, cb) 552 | ``` 553 | and turning data events to plain CPS function outputs: 554 | ```js 555 | const 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 565 | Any async database access API with callbacks can be curried into parametrized CPS functions: 566 | 567 | ```js 568 | const queryDb = (db, query) => callback => getQuery(db, query, callback) 569 | const insertDb = (db, data) => callback => inserData(db, data, callback) 570 | ``` 571 | 572 | In most cases each of these is considered a single request resulting in either success of failure. 573 | However, more general CPS functions can implement more powerful functionality with multiple callback calls. 574 | For instance, a function can run multiple data insetion attempts with progress reported back to client. 575 | Or 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. 576 | Further, the database query funtion can hold a state that is advanced with each call. 577 | Similarly, 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 580 | The [Express Framework](https://expressjs.com/) in NodeJs popularised 581 | the concept of [middleware](https://expressjs.com/en/guide/writing-middleware.html) 582 | that later found its place in other frameworks such as 583 | [Redux](https://redux.js.org/advanced/middleware#understanding-middleware). 584 | In each case, a *middleware* is a special kind of function, 585 | plain in case of Express and curried in case of Redux, 586 | which has one continuation callback among its parameters. 587 | To each middleware in each of these frameworks, 588 | there is the associated parametrized CPS function, 589 | obtained by switching parameters and (un)currying. 590 | As the correspondence `middleware <-> CPS function` goes in both ways, 591 | it allows for each side to benefit from the other. 592 | 593 | 594 | ## Web Sockets 595 | Here is a generic CPS function parametrized by its url `path`: 596 | ```js 597 | const WebSocket = require('ws') 598 | const createWS = path => callback => 599 | new WebSocket(path).on('message', callback) 600 | ``` 601 | 602 | The callback will be called repeatedly with every new socket message emited. 603 | 604 | Other websocket events can be subscribed by other callbacks, 605 | so that a single CPS function with its multiple callbacks 606 | can encapsulate the entire socket functionality. 607 | 608 | 609 | ## Stream libraries 610 | 611 | ### Pull Streams 612 | The [Pull Streams](https://pull-stream.github.io/) 613 | present an ingenious way of implementing 614 | a rich on-demand stream functionality, 615 | including back pressure, 616 | entirely with plain JavaScript functions. 617 | In a way, they gave some of the original inspirations 618 | for the general CPS function pattern. 619 | 620 | Indeed, a Pull Stream is essentially a function `f(abort, callback)` that is called repeatedly 621 | by the sink to produce on-demand stream of data. 622 | Any such function can be clearly curried into a 623 | is a parametrized CPS function 624 | ```js 625 | const pullStream = params => callback => {...} 626 | ``` 627 | 628 | ### [Flyd](https://github.com/paldepind/flyd) 629 | Any `flyd` stream can be wrapped into a CPS function with single callback called with single argument: 630 | ```js 631 | const cpsFun = callback => flydStream 632 | .map(x => callback(x)) 633 | ``` 634 | The resulting CPS function `cpsFun`, when called with any `callback`, 635 | simply subsribes that callback to the stream events. 636 | 637 | Conversely, any CPS function `cpsFun` can be simply called with 638 | any `flyd` stream in place of one of its callback arguments: 639 | ```js 640 | let x = flyd.stream() 641 | cpsFun(x) 642 | ``` 643 | That will push the first argument of any callback call of `x` into the stream. 644 | 645 | 646 | ## Event aggregation 647 | Similarly to [`flyd` streams](https://github.com/paldepind/flyd/#creating-streams), 648 | CPS functions can subscribe their callbacks to any event listener: 649 | ```js 650 | const cpsFun = callback => 651 | document.getElementById('button') 652 | .addEventListener('click', callback) 653 | ``` 654 | Furthermore, more complex CPS functions can similarly subscribe to 655 | muiltiple events: 656 | ```js 657 | const cpsFun = (cb1, cb2) => { 658 | document.getElementById('button1') 659 | .addEventListener('click', cb1) 660 | document.getElementById('button2') 661 | .addEventListener('click', cb2) 662 | } 663 | ``` 664 | and thereby serve as functional event aggregators 665 | encapsulating multiple events. 666 | Every time any of the event is emitted, 667 | the corresponding callback will fire 668 | with entire event data passed as arguments. 669 | That way complete information from multiple events 670 | remains accessible via single CPS function. 671 | 672 | 673 | 674 | # Comparison with Promises and Callbacks 675 | 676 | Our main motivation for dealing with CPS functions is to enhance 677 | the power of common coding patterns into a single unified abstraction, 678 | which can capture the advantages typically regarded as ones of Promises over callbacks. 679 | 680 | In 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 682 | advantages of Promises over callbacks, 683 | that we would like to consider here in the light of the CPS functions 684 | and 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 | 689 | We regard the CPS functions returning their output in similar fashion as promises, 690 | via the arguments inside each callback call. 691 | Recall that a result inside promise can only be extracted via a callback, 692 | which is essentially the same as passing the callback to a CPS function: 693 | ```js 694 | // pass callbacks to promise 695 | const promise.then(cb1, cb2) 696 | // => result is delivered via cb1(result) 697 | ``` 698 | ```js 699 | // pass callbacks to CPS function 700 | const cps(f1, f2) 701 | // => a tuple (vector) of results is deliverd via f1(res1, res2, ...) 702 | ``` 703 | Thus, CPS functions can be regarded as generalization of promises, 704 | where callbacks are allowed to be called multiple times with several arguments each time, 705 | rather than with a single value. 706 | Note 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 713 | asyncFunction1(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 | 723 | In our view, the complexity of chaing for the callbacks is merely due to lacking convenience methods for doing it. 724 | On a basic level, a Promise wraps a CPS function into an object providing such methods. 725 | However, the Promise constructor also adds limitations on the functionality and generally does a lot more, sometimes at the cost of performance. 726 | On the other hand, to have similar chaining methods, much less powerful methods are needed, 727 | that can be uniformly provided for general CPS functions. 728 | The above example can then be generalized to arbitrary CPS functions: 729 | ```js 730 | // wrapper providing methods 731 | CPS(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 | ``` 742 | Here `CPS(...)` is a lightweight object wrapper providing the `map` and `chain` methods among others, 743 | such 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). 744 | At 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 | 751 | Similar to promises wrapping their data, 752 | we regard the CPS functions as wrapping the outputs of their callbacks. 753 | Whenever methods are needed, a CPS function can be explicitly wrapped into 754 | its CPS object via the `CPS`, 755 | similar to how the `Promise` constructor wraps its producer function, 756 | except that `CPS` does nothing else. 757 | There is no recursive unwrapping of "thenables" nor other promises as with 758 | the Promise constructor. 759 | 760 | In addition, the CPS object `CPS(cpsFunction)` retains the same information 761 | by delivering the same functionality via direct funtion calls with the same callbacks! 762 | That is, the following calls are identical: 763 | ```js 764 | cpsFunction(cb1, cb2, ...) 765 | CPS(cpsFunction)(cb1, cb2, ...) 766 | ``` 767 | That means, the wrapped CPS function can be dropped directly into the same code 768 | preserving all the functionality with no change! 769 | 770 | In regard of composing asynchronous calls, with CPS functions it can be as simple as in 771 | the 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 | 777 | In regards of error handling, 778 | the following paragraph in here http://exploringjs.com/es6/ch_promises.html#_chaining-and-errors 779 | seems 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 783 | asyncFunc1() 784 | .then(asyncFunc2) 785 | .then(asyncFunc3) 786 | .catch(function (reason) { 787 | // Something went wrong above 788 | }); 789 | ``` 790 | 791 | And here is the same example with CPS functions: 792 | ```js 793 | CPS(cpsFunc1) 794 | .chain(cpsFunc2) 795 | .chain(cpsFunc3) 796 | .map(null, reason => { 797 | // Something went wrong above 798 | }); 799 | ``` 800 | Here the `map` method is used with two arguments 801 | and the second callback considered as holding errors, 802 | in the same way as the Promises achieve that effect. 803 | 804 | There is, however, no a priori restriction for the error callback 805 | to be the second argument, it can also be the first callback 806 | as 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 | 808 | Similar to Promises, also for CPS functions, handling 809 | both exceptions and asynchronous errors can be managed the same uniform way. 810 | Or the multiple callbacks feature of CPS functions can be utilized 811 | to handle errors of different nature in different callbacks, 812 | such as for instance [Fun-Task does](https://github.com/rpominov/fun-task/blob/master/docs/exceptions.md). 813 | 814 | On the other hand, in contrast to Promises, 815 | the CPS functions allow for clean separation between exceptions such as bugs 816 | that need to be caught as early as possible, and asynchronous errors 817 | that are expected and returned via the error callbacks calls. 818 | The 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 | 824 | The "curried nature" of the (parametrized) CPS functions 825 | ensures clean separation between their input parameters 826 | and the callbacks that are used to hold the output only: 827 | ```js 828 | const paramCps = (param1, param2, ...) => (cb1, cb2, ...) => { ... } 829 | ``` 830 | Here the output holding callbacks `cb1, cb2, ...` are 831 | cleanly "curried away" from the input parameters `param1, param2, ...`. 832 | 833 | Note that, without currying, it would not be possible to achieve similar separation. 834 | If function is called directly without currying, it is impossible to tell 835 | which arguments are meant for input and which for output. 836 | 837 | The principle here is very analogous to how that separation is achieved by Promises, 838 | except that the CPS function do not impose any restricitons on the 839 | number of their callback calls, nor the number of arguments passed to each callback 840 | with 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 | 846 | The CPS functions build directly on the standard already established for JavaScript functions. 847 | The 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 | 854 | The `CPS` function transforms any CPS function into that very same CPS function, 855 | to which in addition all API methods can be applied. 856 | The same methods are provided on the `CPS` namespace and 857 | can be applied directly to CPS functions with the same effect. 858 | For instance, the following expressions are equivalent ([in the sense of fantasyland](https://github.com/fantasyland/fantasy-land#terminology)): 859 | ```js 860 | CPS(cpsFun).map(f) 861 | map(f)(cpsFun) 862 | map(f, cpsFun) 863 | ``` 864 | Note that the functional style let us simply drop in CPS functions as plain functions, 865 | whereas to use `map` as method we need to wrap them into `CPS()` first. 866 | 867 | And the equivalent multiple argument versions are: 868 | ```js 869 | CPS(cpsFun).map(f1, f2, ...) 870 | map(f1, f2, ...)(cpsFun) 871 | map(f1, f2, ..., cpsFun) 872 | ``` 873 | In the last expression, only the last argument is a CPS function, 874 | whereas all other arguments are arbitrary functions 875 | acting by means of their return values. 876 | 877 | 878 | ## Conventions 879 | In the following we slightly abuse the notation by placing methods directly 880 | on the CPS functions, where the meaning is always after wrapping the functions into `CPS`. 881 | That is, we write 882 | ```js 883 | cpsFun.map(f) 884 | // instead of 885 | CPS(cpsFun).map(f) 886 | ``` 887 | We could have used instead the equivalent functional style `map(f)(cpsFun)`, 888 | but the fluent style seems more common in JavaScript and closer to how Promises are used, 889 | so we use it instead. 890 | 891 | 892 | ## CPS.map 893 | The `map` method and the equivalent `CPS.map` function in their simplest form 894 | are similar to `Array.map` as well as other `map` functions/methods used in JavaScript. 895 | 896 | ### Mapping over single function 897 | In the simplest case of a single function `x => f(x)` with one argument, 898 | the corresponding transformation of the CPS function only affects the first callback, 899 | very similar to how the function inside `.then` method of a Promise only affects the fulfilled value: 900 | ```js 901 | const newPromise = oldPromise.then(f) 902 | ``` 903 | 904 | Except that the `map` behavior is simpler with no complex promise recognition nor any thenable unwrapping: 905 | ```js 906 | const newCps = CPS(oldCps).map(f) 907 | // or equivalently in point-free functional style 908 | const newCps = map(f)(oldCps) 909 | // or equivalently using pipeline 910 | const newCps = pipeline(oldCps)(map(f)) 911 | ``` 912 | The `newCps` function will call its first callback 913 | with the single transformed value `f(res)`, 914 | whereas the functionality of the other callbacks remains unchanged. 915 | 916 | Also 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 | 919 | The 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 922 | const 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 927 | const newCps = CPS(x => cb => cb(x+1, x+2)) 928 | .map((val1, val2) => val1 * val2) 929 | // to compare with point-free style 930 | const newCps = map((val1, val2) => val1 * val2)( 931 | x => cb => cb(x+1, x+2) 932 | ) 933 | ``` 934 | 935 | 936 | ### Mapping over multiple functions 937 | 938 | As `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 940 | const newCps = CPS(oldCps).map(res => f(res), err => g(err)) 941 | // or simply 942 | const newCps = CPS(oldCps).map(f, g) 943 | // or equivalently in point-free style 944 | const newCps = map(f, g)(oldCps) 945 | // or with pipeline 946 | const newCps = pipeline(oldCps)(map(f,g)) 947 | ``` 948 | 949 | Here we are calling the second result `err` in analogy with promises, 950 | however, in general, it can be any callback without extra meaning. 951 | The resulting CPS function will call its first and second callbacks 952 | with correspondingly transformed arguments `f(res)` and `g(res)`, 953 | whereas all other callbacks will be passed from `newCps` to `oldCps` unchanged. 954 | 955 | The latter property generalizes the praised feature of Promises, 956 | where a single error handler can deal with all accumulated errors. 957 | In our case, the same behavior occurs for the `n`th callback 958 | that will be picked by only those `map` invocations holding functions at their `n`th spot. 959 | For instance, a possible third callback `progress` 960 | will similaly be handled only invocations of `map(f1, f2, f3)` 961 | with some `f3` provided. 962 | 963 | 964 | ### Map taking arbitrarily many functions with arbitrary numbers of arguments 965 | In most general case, `map` applies its argument functions to several arguments passed at once to corresponding callbacks: 966 | ```js 967 | const 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 971 | const newCps = CPS(oldCps).map(f1, f2, f3) 972 | ``` 973 | 974 | That means, the pattern can be generalized to 975 | ```js 976 | const newCps = CPS(oldCps).map((res1, res2, ...) => f(res1, res2, ...)) 977 | ``` 978 | or passing arbitrary number of arguments with [rest parameters syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters): 979 | ```js 980 | const newCps = CPS(oldCps).map((...args) => f(...args)) 981 | // which is the same as 982 | const newCps = CPS(oldCps).map(f) 983 | ``` 984 | or passing only some of the arguments: 985 | ```js 986 | const newCps = CPS(oldCps) 987 | .map((iAmThrownAway, ...rest) => f(...rest)) 988 | ``` 989 | or picking props from multiple objects selectively via destructuring: 990 | ```js 991 | const newCps = CPS(oldCps) 992 | // select only important props and transform as 2-tuple 993 | .map(({name: name1}, {name: name2}) => f(name1, name2)) 994 | ``` 995 | None of these transformations would be as convenient with Promises where only single values are ever being passed. 996 | 997 | 998 | ### Functor laws 999 | The `map` method for single functions of single argument satisfies the functor laws. 1000 | That is, the following pairs of expressions are equivalent: 1001 | ```js 1002 | cpsFun.map(f).map(g) 1003 | // and 1004 | cpsFun.map(x => g(f(x))) 1005 | ``` 1006 | as well as 1007 | ```js 1008 | cpsFun 1009 | // and 1010 | cpsFun.map(x => x) 1011 | ``` 1012 | 1013 | In fact, we have more general equivalences with multiple arguments: 1014 | ```js 1015 | cpsFun.map(f1, f2, ...).map(g1, g2, ...) 1016 | // and 1017 | cpsFun.map(x1 => g1(f1(x1)), x2 => g2(f2(x2)), ...) 1018 | ``` 1019 | where in addition, the number of `f`'s can differ from the number of `g`'s, 1020 | in which case the missing maps are replaced by the identities. 1021 | 1022 | 1023 | ### CPS.of 1024 | The static method `CPS.of` that we simply call `of` here 1025 | is the simplest way to wrap values into a CPS function: 1026 | ```js 1027 | const of = (x1, x2, ...) => callback => callback(x1, x2, ...) 1028 | ``` 1029 | or equivalently 1030 | ```js 1031 | const of = (...args) => callback => callback(...args) 1032 | ``` 1033 | 1034 | Here the full tuple `(x1, x2, ...)` becomes a single output of 1035 | the created CPS function `of(x1, x2, ...)`. 1036 | 1037 | As mentioned before, 1038 | `of` and `map` for single functions with single argument 1039 | conform to the [Pointed Functor](https://stackoverflow.com/questions/39179830/how-to-use-pointed-functor-properly/41816326#41816326), 1040 | that is the following expressions are equivalent: 1041 | ```js 1042 | of(x).map(f) 1043 | of(f(x)) 1044 | // both expressions are equivalent to 1045 | cb => cb(f(x)) 1046 | ``` 1047 | In our case, the first expression maps `f` over the CPS function `cb => cb(x)` by transforming its single output `x`, 1048 | whereas the second one outputs `f(x)` direclty into its callback, which is obviously the same. 1049 | 1050 | More generally, the following are still equivalent 1051 | with the same reasoning: 1052 | ```js 1053 | of(x1, x2, ...).map(f) 1054 | // and 1055 | of(f(x1, x2, ...)) 1056 | // are equivalent to 1057 | cb => cb(f(x1, x2, ...)) 1058 | ``` 1059 | 1060 | 1061 | ## CPS.chain 1062 | 1063 | ### Transforming multiple arguments into multiple arguments 1064 | There is a certain lack of symmetry with the `map` method, 1065 | due to the way functions are called with several arguments but 1066 | only ever return a single value. 1067 | 1068 | But what if we want not only to consume, but also to pass multiple arguments to the callback of the new CPS function? 1069 | 1070 | No problem. Except that, we should wrap these into another CPS function and use `chain` instead: 1071 | ```js 1072 | const newCps = CPS(oldCps) 1073 | .chain((x1, x2, ...) => of(x1 + 1, x2 * 2)) 1074 | // or explicitly 1075 | const newCps = CPS(oldCps) 1076 | .chain((x1, x2, ...) => cb => cb(x1 + 1, x2 * 2)) 1077 | ``` 1078 | 1079 | Here we pass both `x1 + 1` and `x2 * 2` simultaneously into the callback `cb`. 1080 | Generalizing Promises that only hold one value, we can regard `(x1, x2, ...)` as tuple of values held inside single CPS function, 1081 | in fact, all being passed to only its first callback. 1082 | Now the output values of `oldCps` are passed to the functions inside 1083 | `chain`, get transformed it according to the second CPS function, 1084 | i.e. into the pair `(x1 + 1, x2 * 2)`, 1085 | and finally passed to the first callback of `newCps`. 1086 | The final result is exactly the intended one, that is, 1087 | the result tuple output `(x1, x2, ...)` from `oldCps`'s first callback is transformed into the new pair `(x1 + 1, x2 * 2)` 1088 | that becomes the output of `newCps`. 1089 | 1090 | 1091 | ### Why is it called `chain`? 1092 | Have you noticed the difference between how `map` and `chain` are used? 1093 | Here is the simplest case comparison: 1094 | ```js 1095 | cpsFun.map(x => x+1) 1096 | cpsFun.chain(x => of(x+1)) 1097 | ``` 1098 | In the first expression the value `x+1` is passed directly, 1099 | in the second it is wrapped into CPS function with `of`. 1100 | The first time the return value of the function inside `map` is used, 1101 | the second time it is the output value of the CPS function inside `chain`. 1102 | 1103 | The "Promised" way is very similar: 1104 | ```js 1105 | promise.then(x => x+1) 1106 | promise.then(x => Promise.resolve(x+1)) 1107 | ``` 1108 | Except that both times `then` is used, 1109 | so we don't have to choose between `map` and `chain`. 1110 | However, such simplicity comes with its cost. 1111 | Since `then` has to do its work to detect 1112 | and recursively unwrap any Promise or in fact any ["thenable"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve), 1113 | there can be loss in performance as well as 1114 | in safety to refactor 1115 | ```js 1116 | promise.then(f).then(g) 1117 | ``` 1118 | to 1119 | ```js 1120 | promise.then(x => g(f(x))) 1121 | ``` 1122 | which is [not always the same](https://stackoverflow.com/a/50173415/1614973). 1123 | 1124 | On the other hand, our `map` method 1125 | conforms to the Functor composition law, 1126 | that is the following expressions are always equivalent 1127 | and safe to refactor to each other (as mentioned above): 1128 | ```js 1129 | cpsFun.map(f).map(g) 1130 | // and 1131 | cpsFun.map(x => g(f(x))) 1132 | ``` 1133 | And since now no other work is involved, 1134 | performance wins. 1135 | 1136 | However, if we try use `map` in the second case, 1137 | instead of `chain`, we get 1138 | ```js 1139 | cpsFun.map(x => of(x+1)) 1140 | ``` 1141 | which emits `of(x+1)` as output, rather than `x+1`. 1142 | That is, the output result of our CPS function is another CPS function, 1143 | so we get our value wrapped twice. 1144 | This is where `chain` becomes useful, 1145 | in that in removes one of the layers, 1146 | aka "flattens" the result, 1147 | which is why it is also called `flatMap`. 1148 | 1149 | For CPS functions, the name `chain` is particularly descriptive 1150 | because it effectively chains two such functions 1151 | by passing the output of one funciton as input to the other. 1152 | And the rule becomes very simple: 1153 | 1154 | *Use `map` with "plain" functions and `flatMap` with CPS functions inside* 1155 | 1156 | 1157 | ### Composing multiple outputs 1158 | In the previous case we had `chain` over a CPS function with single output, 1159 | even when the output itself is a tuple. 1160 | In comparison, a general Promise has two outputs - the result and the error. 1161 | Of course, in case of Promises, there are more restrctions such as only one of these two 1162 | outputs can be emitted. 1163 | 1164 | No such restrictions are iposed on CPS functions, 1165 | where two or more callbacks can receive arbitrary number of outputs arbitrarily often. 1166 | To keep things simple, consider how the Promise functionality can be extended 1167 | without the output exclusivity restriction: 1168 | ```js 1169 | const cpsFun = (cb1, cb2) => { 1170 | /* some work here... */ 1171 | cb1(res1) 1172 | /* some more work... */ 1173 | cb2(res2) 1174 | } 1175 | ``` 1176 | So both callbacks are called with their individual results at different moments. 1177 | 1178 | A very useful and realistic example of this functionality would be, 1179 | when the server sent an error but then eventually managed to deliver the data. 1180 | That would be impossible to implement with Promises. 1181 | 1182 | Back to our example, 1183 | we now want to use the output for the next CPS computation: 1184 | ```js 1185 | const newCps = cpsFun.chain(res => anotherCps(res)) 1186 | ``` 1187 | We are now chaining aka sequentially executing `cpsFun`, 1188 | followed by the next parametrized CPS function 1189 | `anotherCps` applied to the result `res` of the previous computation. 1190 | 1191 | So how should we combine both computations? 1192 | And should we apply the second one to `res1` or `res2`? 1193 | 1194 | If you read the above description of the `map`, you know the answer. 1195 | The principle is the same. As we only pass one function to `chain`, 1196 | only the first callback is affected. 1197 | That is, we must pass only the first result `res1` to `anotherCps`. 1198 | Whose output will be our final result inside the first callback, 1199 | whereas all other callbacks remain unchanged. 1200 | 1201 | So the functionality of `newCps` is equivalent to the following: 1202 | ```js 1203 | const newCps = (...args) => cpsFun( 1204 | res1 => anotherCps(res1)(...args), 1205 | res2 => cb2(args[1]) 1206 | ) 1207 | ``` 1208 | Note how all callbacks are passed to the inner CPS function in the same order as `args`. 1209 | That guarantees that no outgoing information from `anotherCps` can ever get lost. 1210 | 1211 | 1212 | ### Passing multiple CPS functions to `chain` 1213 | 1214 | Similarly to `map`, also `chain` accepts arbitrary number of functins, 1215 | this time CPS functions: 1216 | ```js 1217 | const newCps = oldCps.chain( 1218 | res1 => anotherCps1(res1), 1219 | res2 => anotherCps2(res2), 1220 | ... 1221 | ) 1222 | ``` 1223 | Look how similar it is with Promises usage: 1224 | ```js 1225 | // Promises 1226 | promise.then( 1227 | res => anotherPromise(res), 1228 | error => errorHandlerPromise(err) 1229 | ) 1230 | // CPS functions 1231 | cpsFun.chain( 1232 | res => anotherCps(res), 1233 | err => errorHandlerCps(err) 1234 | ) 1235 | ``` 1236 | You can barely tell the difference, can you? 1237 | 1238 | Or, since the CPS functions are just functions, 1239 | we can even drop any Promise `then` function directly into `chain`: 1240 | ```js 1241 | // CPS functions 1242 | cpsFun.chain( 1243 | res => anotherPromise.then(res), 1244 | error => errorHandlerPromise.then(err) 1245 | ) 1246 | // or just the shorter 1247 | cpsFun.chain( anotherPromise.then, errorHandlerPromise.then ) 1248 | ``` 1249 | 1250 | On the other hand, the CPS functions are more powerful 1251 | in that they can call their callbacks multiple times in the future, 1252 | with the potential of passing further important information back to the caller. 1253 | Also we don't want to prescribe in which callback the error should go, 1254 | treating all the callbacks in a uniform way. 1255 | Here is some pseudocode demonstrating general usage of `chain` 1256 | with mulitple functions: 1257 | ```js 1258 | const newCps = oldCps.chain( 1259 | (x1, x2, ...) => cpsFun1(x1, x2, ...), 1260 | (y1, y2, ...) => cpsFun2(y1, y2, ...), 1261 | ... 1262 | ) 1263 | ``` 1264 | And the functionality of `newCps` is equivalent to 1265 | ```js 1266 | const newCps = (cb1, cb2, ...) => oldCps( 1267 | (x1, x2, ...) => cpsFun1(x1, x2, ...)(cb1, cb2, ...) 1268 | (y1, y2, ...) => cpsFun1(y1, y2, ...)(cb1, cb2, ...) 1269 | ... 1270 | ) 1271 | ``` 1272 | As any other CPS function, our `newCps` accepts arbitrary number of callbacks 1273 | that are simply all passed to each interior CPS function in the same order. 1274 | That leads to the effect of capturing output events from each of them, 1275 | and "flattening" the event streams via simple merging. 1276 | 1277 | 1278 | ### Monadic laws 1279 | When used with single callback argument, 1280 | `of` and `chain` satisfy the regular monadic laws. 1281 | 1282 | #### Associativity law 1283 | The associativity law analogue for Promises would be the equivalence of 1284 | ```js 1285 | promise 1286 | .then(x1 => promise1(x1)) 1287 | .then(x2 => promise2(x2)) 1288 | // and 1289 | promise 1290 | .then(x1 => promise1.then(x2 => promise2(x2))) 1291 | ``` 1292 | which [are, however, not always equivalent](https://stackoverflow.com/a/50173415/1614973). 1293 | 1294 | 1295 | For CPS functions in contrast, we do indeed obtain true equivalence of 1296 | ```js 1297 | cpsFun 1298 | .chain(x1 => cpsFun1(x1)) 1299 | .chain(x2 => cpsFun2(x2)) 1300 | // and 1301 | cpsFun 1302 | .chain(x1 => cpsFun1.chain(x2 => cpsFun2(x2))) 1303 | ``` 1304 | Because, since these are just functions, 1305 | both expressions can be direclty expanded into 1306 | ```js 1307 | cb => cpsFun( 1308 | x1 => cpsFun1(x1)( 1309 | x2 => cpsFun2(x2)(cb) 1310 | ) 1311 | ) 1312 | ``` 1313 | That is, the output `x1` of `cpsFun` is passed to `cpsFun1`, 1314 | which transforms it into `x2` as output, 1315 | subsequently passed to `cpsFun2`, 1316 | whose output is finally diverted direclty into `cb`. 1317 | 1318 | More generally, similar law still holds for mulitple arguments, 1319 | that is the following are equivalent 1320 | ```js 1321 | cpsFun 1322 | .chain(f1, f2, ...) 1323 | .chain(g1, g2, ...) 1324 | // and 1325 | cpsFun 1326 | .chain( 1327 | (...xs) => f1(...xs).chain((...ys) => g1(...ys)), 1328 | (...xs) => f2(...xs).chain((...ys) => g2(...ys)), 1329 | ... 1330 | ) 1331 | ``` 1332 | as 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 1342 | The monadic identity laws asserts that both following 1343 | expressions are equivalent to the CPS function `cpsFun`: 1344 | ```js 1345 | cpsFun 1346 | // and 1347 | chain(y => of(y))(cpsFun) 1348 | ``` 1349 | Here `cpsFun` is any CPS function, 1350 | whose output is composed with the CPS identity `y => of(y)`. 1351 | 1352 | 1353 | On the other hand, taking a parametrized CPS function 1354 | `x => cpsFun(x)` and moving the identity to other side, 1355 | we get the other law asserting the equivalence of: 1356 | ```js 1357 | x => cpsF(x) 1358 | // is equivalent to 1359 | x => chain( 1360 | y => cpsF(y) 1361 | )( 1362 | cb => cb(x) 1363 | ) 1364 | ``` 1365 | 1366 | Once expanded, both equivalences are 1367 | became straightforward to check. 1368 | More interestingly, they still hold for multiple arguments: 1369 | ```js 1370 | cpsFun 1371 | // is equivalent to 1372 | cpsFun.chain( 1373 | (...ys) => of(...ys), 1374 | (...ys) => of(...ys), 1375 | ... /* any number of identities */ 1376 | ) 1377 | ``` 1378 | and 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 1389 | The Node style callbacks with error argument first 1390 | force their errors to be handled each single time: 1391 | ```js 1392 | someNodeFunction(param, callback((error, result) => { 1393 | if (error) mustHandle... 1394 | doMoreWork(result, callback((error, result) => { 1395 | ... 1396 | })) 1397 | })) 1398 | ``` 1399 | In constrast, Promises make it possible to handle all errors with one callback at the end: 1400 | ```js 1401 | promise 1402 | .then(doSomeWork) 1403 | .then(doMoreWork) 1404 | ... 1405 | .catch(handleAllErrors) 1406 | ``` 1407 | 1408 | Many libraries offering methods to "promisify" Node style callbacks. 1409 | The 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. 1410 | For instance, it is perfectly legal to call callbacks muiltiple times 1411 | (for which there are many use cases such as streams and event handlers), 1412 | whereas the "promisification" would only see the first call. 1413 | 1414 | On the other hand, we can curry any callback-last Node method into CPS function 1415 | ```js 1416 | const cpsErrback = (...args) => cb => nodeApi(...args, cb) 1417 | ``` 1418 | and subsequently `chain` it into "Promise" style CPS function 1419 | with the same pair of callbacks, except that no other functionality is added nor removed: 1420 | ```js 1421 | const promiseStyle = CPS(cpsErrback) 1422 | .chain((error, ...results) => (resBack, errBack) => error 1423 | ? errBack(error) 1424 | : resBack(...results) 1425 | ) 1426 | ``` 1427 | 1428 | Now we can chain these CPS funcitons exactly like Promises, 1429 | passing only the first callback, and handle all errors at the end in the second callback. 1430 | ```js 1431 | promiseStyle 1432 | .chain(doSomeWork) 1433 | .map(doMoreWork) 1434 | ... 1435 | .chain(null, handleAllErrors) 1436 | 1437 | ``` 1438 | 1439 | 1440 | 1441 | ## CPS.ap (TODO) 1442 | The `ap` operator plays an important role when 1443 | *running functions in parallel* and combining their outputs. 1444 | 1445 | ### Running CPS functions in parallel 1446 | Similarly 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, 1448 | delivered via callbacks. 1449 | A simple example is getting result from a database query via `cpsDb` function 1450 | and display it with via function transformer obtained from an independent query: 1451 | ```js 1452 | // returns result via 'cb(result)' call 1453 | const cpsDb = query => cb => getQuery(query, cb) 1454 | // returns transformer function via 'cb(transformer)' 1455 | const cpsTransformer = path => cb => getTransformer(path, cb) 1456 | // Now use the 'ap' operator to apply the transformer to the query result: 1457 | const getTransformedRes = (query, path) => CPS(cpsDb(query)).ap(cpsTransformer(path)) 1458 | // or equivalently in the functional style, without the need of the 'CPS' wrapper: 1459 | const getTransformedRes = (query, path) => ap(cpsTransformer(path))(cpsDb(query)) 1460 | ``` 1461 | 1462 | Note that we could have used `map` and `chain` to run the same functions sequentially, 1463 | one after another: 1464 | ```js 1465 | (query, path) => CPS(cpsDb(query)) 1466 | .chain(result => cpsTransformer(path) 1467 | .map(transformer => transformer(result))) 1468 | ``` 1469 | Here we have to nest, in order to keep `result` in the scope of the second function. 1470 | However, `result` from the first function was not needed to run the `cpsTransformer`, 1471 | so it was a waste of time and resources to wait for the query `result` 1472 | before getting the `transformer`. 1473 | It would be more efficient to run both functions in parallel and then combine the results, 1474 | which is precisely what the `ap` operator does. 1475 | 1476 | 1477 | ### Lifting functions of multiple parameters 1478 | Perhaps the most important use of the `ap` operator is lifting plain functions 1479 | to act on results of CPS functional computations. 1480 | That way simple plain functions can be created and re-used 1481 | with arbitrary data, regardless of how the data are retrieved. 1482 | In the above example we have used the general purpose plain function 1483 | ```js 1484 | const f =(result, transformer) => transformer(result) 1485 | ``` 1486 | which is, of course, just the ordinary function call. 1487 | That function was "lifted" to act on the data delivered as outputs 1488 | from separate CPS functions. 1489 | 1490 | Since this use case is very common, 1491 | we have the convenience operator doing exactly that called `lift`: 1492 | ```js 1493 | const getTransformedRes = (query, path) => 1494 | lift(transformer => transformer(result)) 1495 | (cpsDb(query), cpsTransformer(path)) 1496 | ``` 1497 | 1498 | #### Promise.all 1499 | The common way to run Promises in parallel via `Promise.all` 1500 | is a special case of the `lift` usage, 1501 | corresponding to lifting the function 1502 | combining values in array: 1503 | ```js 1504 | const combine = (...args) => args 1505 | Promise.all = promiseArray => lift(combine)(...promiseArray) 1506 | ``` 1507 | 1508 | Similarly, the same `combine` function (or any other) can be lifted 1509 | over 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 1516 | Note that `lift` (and `ap`) are best used when 1517 | their arguments can only be retrieved as outputs from separate CPS functions. 1518 | If for instance, both `result` and `transformer` 1519 | can be delivered via single query, 1520 | using `lift` would be a waste of its parallel execution functionality. 1521 | Instead we could have used the simple `map` with a single CPS function: 1522 | ```js 1523 | const getTransformedResSingle = (query, path) => 1524 | CPS(getData(query, path)).map((result, transformer) => transformer(result)) 1525 | ``` 1526 | Note how the `map` function is applied with two arguments, 1527 | which assumes the `getData` function to have these in a single callback output 1528 | as `callback(result, transformer)`. 1529 | 1530 | 1531 | ### Applying multiple functions inside `ap` 1532 | 1533 | As with `map` and `chain`, the same rules apply for `ap`: 1534 | ```js 1535 | const transformed = CPS(cpsFun).ap(F1, F2, ...) 1536 | ``` 1537 | When called with callbacks `(cb1, cb2, ...)`, 1538 | the output from `cb1` is transformed with the output function from `F1`, 1539 | the output from `cb2` with function from `F2` and so on. 1540 | 1541 | For instance, a CPS function with two callbacks such as `(resBack, errBack)` 1542 | can be `ap`ed over a pair of CPS functions, outputting plain functions each 1543 | ```js 1544 | // These call some remote API 1545 | const cpsResTransformer = cb => getResTransformer(cb) 1546 | const cpsErrHandler = cb => getErrHandler(cb) 1547 | // This requires error handlers 1548 | const 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 1551 | CPS(cpsValue).ap(cpsResTransformer, cpsErrHandler)( 1552 | res => console.log("Transformed Result: ", res) 1553 | err => console.log("The Error had been handled: ", err) 1554 | ) 1555 | ``` 1556 | 1557 | The above pattern can be very powerful, 1558 | for instance the `cpsErrHandler` function 1559 | can include a remote retry or cleanup service 1560 | that is now completely abstracted away from the main code pipeline! 1561 | 1562 | 1563 | 1564 | ### Applicative laws 1565 | The `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) 1570 | The `merge` operator merges outputs events from multiple CPS functions, 1571 | which occurs separately for each callback slot: 1572 | ```js 1573 | // merge outputs into single CPS function 1574 | const cpsMerged = merge(cpsF1, cpsF2, ...) 1575 | // cb1 receives all outputs from the first callback of each of the cpsF1, cpsF2, ... 1576 | cpsMerged(cb1, cb2, ...) 1577 | ``` 1578 | Here the `N`-th callback of `cpsMerged` gets called each time 1579 | the `N`-th callback of any of the functions `cpsF1`, `cpsF2`, ..., 1580 | with the same arguments. 1581 | This behaviour corresponds to merging the values emitted by 1582 | each event stream. 1583 | 1584 | ### Relation with Promise.race 1585 | The `merge` operator generalizes the functionality provided for Promises via `Promise.race`. 1586 | Since Promises only take the first emitted value from each output, 1587 | merging those results in the earliest value from all being picked by the Promise, 1588 | hence the direct analogy with `Promise.race`. 1589 | 1590 | ### Commutative Monoid 1591 | The `merge` operator makes the set of all CPS functions a commutative Monoid, 1592 | where the identity is played by the trivial CPS function that never emits any output. 1593 | 1594 | 1595 | 1596 | 1597 | ## CPS.filter 1598 | The `filter` operator does the obvious thing, 1599 | that is trasform one CPS function into another by filtering the output. 1600 | As the output may have several arguments, 1601 | the filter function is also variadic: 1602 | ```js 1603 | const cpsFiltered = filter(pred)(cpsFun) 1604 | ``` 1605 | Here `pred` is a Boolean function called for each output tuple. 1606 | The resulting `cpsFiltered` emits only the output for which `pred` returns `true`. 1607 | 1608 | 1609 | ### Filtering over multiple functions 1610 | Consistently with other operators, 1611 | also `filter` accepts multiple predicate functions, 1612 | each matched against the ouput from the corresponding callback. 1613 | 1614 | That is, the filtered function 1615 | ```js 1616 | const cpsFiltered = filter(p1, p2, ...)(cpsFun) 1617 | ``` 1618 | when passed callbacks `(cb1, cb2, ...)`, 1619 | calls `cb1` with the same output `(x1, x2, ...)` as `cpsFun` does, 1620 | as long as `p1(x1, x2, ...)` returns `true`, otherwise the call is skipped. 1621 | Similarly, `p2` filters the output of `cb2` and so on. 1622 | The callbacks not corresponding to any predicate function 1623 | will be unaffected and the predicates corresponding to no callbacks 1624 | are ignored. 1625 | 1626 | 1627 | ### Implementation via `chain` 1628 | Filtering is really chaining: 1629 | ```js 1630 | // pass through only input truthy `pred` 1631 | const cpsFilter = pred => (...input) => cb => { 1632 | if (pred(...input)) cb(...input) 1633 | } 1634 | // now chain with `cpsFilter(pred)`: 1635 | const filter = pred => CPS(cpsFun) 1636 | .chain(cpsFilter(pred)) 1637 | ``` 1638 | And 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 1641 | const filter = (...pred) => CPS(cpsFun) 1642 | .chain(...pred.map(cpsFilter)) 1643 | // or using the `pipeline` operator 1644 | const filter = (...pred) => pipeline(cpsFun)( 1645 | chain(...pred.map(cpsFilter)) 1646 | ) 1647 | ``` 1648 | 1649 | 1650 | 1651 | 1652 | ## CPS.scan 1653 | The `scan` operator acts as "partial reduce" for each output. 1654 | Important example is the stream of states affected by stream of actions: 1655 | ```js 1656 | const cpsState = scan(f)(initState)(cpsAction) 1657 | ``` 1658 | Here `f` is the reducing function accepting current `state` and 1659 | the next `action` and returning the next state as `f(state, action)`, 1660 | that is the signature is 1661 | ```js 1662 | f :: (state, action) -> state 1663 | ``` 1664 | 1665 | Similarly to `filter`, `scan` can also be derived from `chain`: 1666 | ```js 1667 | const 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 | ``` 1675 | Note that the function inside `chain` updates the `state` outside its scope, 1676 | so it is not pure, however, we can still `chain` it like any other function. 1677 | 1678 | And here is the mulitple arguments generalization: 1679 | ```js 1680 | // `reducers` and `states` are matched together by index 1681 | const 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 | ``` 1691 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dmitri Zaitsev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 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 | [![codebeat badge](https://codebeat.co/badges/8cd1b450-262f-4aa1-be1a-d44596f2777e)](https://codebeat.co/projects/github-com-dmitriz-tiny-cps-master) 11 | [![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) 12 | [![dependencies](https://david-dm.org/dmitriz/tiny-cps.svg)](https://david-dm.org/dmitriz/tiny-cps) 13 | [![devDependencies](https://badgen.now.sh/david/dev/dmitriz/tiny-cps)](https://david-dm.org/dmitriz/tiny-cps?type=dev) 14 | [![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) 15 | [![MIT License](https://img.shields.io/npm/l/tiny-cps.svg?style=flat-square)](http://opensource.org/licenses/MIT) 16 | [![Greenkeeper badge](https://badges.greenkeeper.io/dmitriz/tiny-cps.svg)](https://greenkeeper.io/) 17 | 18 | Tiny but powerful goodies for Continuation-Passing-Style (CPS) functions 19 | 20 | # Migration notice 21 | The more recent and advanced version of this package has migrated to 22 | https://www.npmjs.com/package/cpsfy 23 | 24 | Please use `cpsfy` instead of `tiny-cps` 25 | 26 | ```sh 27 | // older package, will remain unchanged 28 | npm install tiny-cps 29 | ``` 30 | *No dependency policy.* 31 | For maximum security, this package is intended not to have dependencies ever. 32 | 33 | ## CPS function 34 | Any function 35 | ```js 36 | //cb1, cb2, ... are called any number of times with any 37 | // (possibly varying each time) number of arguments 38 | const cpsFn = (cb1, cb2, ...) => { ... } 39 | ``` 40 | 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: 41 | ```js 42 | // (x1, ..., xm) is output from nth callback whenever 43 | cbn(x1, ..., xm) // is called 44 | ``` 45 | 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. 46 | 47 | 48 | ## API in brief 49 | ```js 50 | const { map, chain, filter, scan, CPS, pipeline } 51 | = require('tiny-cps') 52 | ``` 53 | Each of the `map`, `chain`, `filter`, `scan` operators can be used in 3 ways: 54 | ```js 55 | // 'map' as curried function 56 | map(f)(cpsFn) 57 | // 'map' method provided by the 'CPS' wrapper 58 | CPS(cpsFn).map(f) 59 | // 'cpsFn' is piped into 'map(f)' via 'pipeline' operator 60 | pipeline(cpsFn)(map(f)) 61 | ``` 62 | 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: 63 | ```js 64 | CPS(cpsFn)(f1, f2, ...) // is equivalent to 65 | cpsFn(f1, f2, ...) 66 | ``` 67 | 68 | #### chaining 69 | ```js 70 | // as methods 71 | CPS(cpsFn).map(f).chain(g).filter(h) 72 | 73 | // of as functional pipeline 74 | pipeline(cpsFn)( 75 | map(f), 76 | chain(g), 77 | filter(h) 78 | ) 79 | ``` 80 | 81 | ### `map(...functions)(cpsFunction)` 82 | ```js 83 | map(f1, f2, ...)(cpsFn) 84 | CPS(cpsFn).map(f1, f2, ...) 85 | pipeline(cpsFn)(map(f1, f2, ...)) 86 | ``` 87 | For each `n`, apply `fn` to each output from the `n`th callback of `cpsFn`. 88 | 89 | #### Result of applying `map` 90 | New CPS function that calls its `n`th callback `cbn` as 91 | ```js 92 | cbn(fn(x1, x2, ...)) 93 | ``` 94 | whenever `cpsFn` calls its `n`th callback. 95 | 96 | #### Example of `map` 97 | ```js 98 | const fs = require('fs') 99 | const readFile = (file, encoding) => 100 | cb => fs.readFile(file, encoding, cb) // CPS function 101 | 102 | // read file and convert all letters to uppercase 103 | const getCaps = map(str => str.toUpperCase())( 104 | readFile('message.txt', 'utf8') 105 | ) 106 | // or 107 | const getCaps = CPS(readFile('message.txt', 'utf8')) 108 | .map(str => str.toUpperCase()) 109 | // or 110 | const getCaps = pipeline(readFile('message.txt', 'utf8'))( 111 | map(str => str.toUpperCase()) 112 | ) 113 | 114 | // getCaps is CPS function, call with any callback 115 | getCaps((err, data) => err 116 | ? console.error(err) 117 | : console.log(data) 118 | ) // => file content is capitalized and printed 119 | ``` 120 | 121 | ### `chain(...functions)(cpsFunction)` 122 | ```js 123 | chain(f1, f2, ...)(cpsFn) 124 | CPS(cpsFn).chain(f1, f2, ...) 125 | pipeline(cpsFn)(chain(f1, f2, ...)) 126 | ``` 127 | where each `fn` is a curried function 128 | ```js 129 | // fn(x1, x2, ...) is expected to return a CPS function 130 | const fn = (x1, x2, ...) => (cb1, cb2, ...) => { ... } 131 | ``` 132 | 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. 133 | 134 | #### Result of applying `chain` 135 | 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`: 136 | ```js 137 | cbm(y1, y2, ...) // is called whenever 138 | cbmFn(y1, y2, ...) // is called where 139 | // cbmFn is the mth callback of fn 140 | ``` 141 | 142 | #### Example of `chain` 143 | ```js 144 | const writeFile = (file, encoding, content) => 145 | // CPS function 146 | cb => fs.readFile(file, encoding, content, cb) 147 | 148 | const copy = chain( 149 | // function that returns CPS function 150 | text => writFile('target.txt', 'utf8', text) 151 | )( 152 | readFile('source.txt', 'utf8') // CPS function 153 | ) 154 | // or 155 | const copy = CPS(readFile('source.txt', 'utf8')) 156 | .chain(text => writFile('target.txt', 'utf8', text)) 157 | // or 158 | const copy = pipeline(readFile('source.txt', 'utf8'))( 159 | chain(text => writFile('target.txt', 'utf8', text)) 160 | ) 161 | 162 | // copy is a CPS function, call it with any callback 163 | copy((err, data) => err 164 | ? console.error(err) 165 | : console.log(data) 166 | ) // => file content is capitalized and printed 167 | ``` 168 | 169 | ### `filter(...predicates)(cpsFunction)` 170 | ```js 171 | filter(pred1, pred2, ...)(cpsFn) 172 | CPS(cpsFn).filter(pred1, pred2, ...) 173 | pipeline(cpsFn)(filter(pred1, pred2, ...)) 174 | ``` 175 | where each `predn` is the `n`th predicate function used to filter output from the `n`th callback of `cpsFn`. 176 | 177 | #### Result of applying `chain` 178 | 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 179 | ```js 180 | predn(x1, x2, ...) == true 181 | ``` 182 | 183 | #### Example of `filter` 184 | ```js 185 | // only copy text if it is not empty 186 | const copyNotEmpty = CPS(readFile('source.txt', 'utf8')) 187 | .filter(text => text.length > 0) 188 | .chain(text => writFile('target.txt', 'utf8', text)) 189 | 190 | // copyNotEmpty is CPS function, call with any callback 191 | copyNotEmpty(err => console.error(err)) 192 | ``` 193 | 194 | ### `scan(...reducers)(...initialValues)(cpsFunction)` 195 | 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. 196 | ```js 197 | scan(red1, red2, ...)(x1, x2, ...)(cpsFn) 198 | (cpsFn).scan(red1, red2, ...)(x1, x2, ...) 199 | pipeline(cpsFn)(scan(red1, red2, ...)(x1, x2, ...)) 200 | ``` 201 | where each `redn` is a *reducer* 202 | ``` 203 | // compute new accumulator value from the old one 204 | // and the tuple of current values (y1, y2, ...) 205 | const redn = (acc, y1, y2, ...) => ... 206 | ``` 207 | 208 | #### Result of applying `scan` 209 | 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. 210 | 211 | 212 | #### Example of `scan` 213 | ```js 214 | // CPS function with 2 callbacks, a click on one 215 | // of the buttons sends '1' into respective callback 216 | const getVotes = (onUpvote, onDownvote) => { 217 | upvoteButton.addEventListener('click', 218 | ev => onUpvote(1) 219 | ) 220 | downvoteButton.addEventListener('click', 221 | ev => onDownvote(1) 222 | ) 223 | } 224 | const add = (acc, x) => acc + x 225 | // count numbers of up- and downvotes and 226 | // pass into respective callbacks 227 | const countVotes = scan(add, add)(0, 0)(getVotes) // or 228 | const countVotes = CPS(getVotes).scan(add, add)(0, 0) 229 | 230 | // countVotes is CPS function that we can call 231 | // with any pair of callbacks 232 | countVotes( 233 | upvotes => console.log(upvotes, ' votes for'), 234 | downvotes => console.log(downvotes, ' votes against'), 235 | ) 236 | ``` 237 | 238 | 239 | ## More details? 240 | 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). 241 | 242 | 243 | 244 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* ----- General purpose operators ----- */ 2 | 3 | /** 4 | * Pipeline Operator 5 | * passes tuple of values to sequence of functions 6 | * similar to the UNIX pipe (x1, ..., xn) | f1 | f2 | ... | fm 7 | * 8 | * @name pipeline 9 | * @params {Tuple} (...args) tuple of arbitrary values 10 | * @curriedParams {Tuple of Functions} (...fns) tuple of functions that 11 | * @returns {value} fn(...f2(f1(...args))...) 12 | * where fns = [f1, f2, ..., fn] 13 | * 14 | * # Examples 15 | * 16 | * pipeline(x)(f1, f2, f3) 17 | * is equivalent to f3(f2(f1(x))) 18 | * pipeline(x,y)(f, g) 19 | * is equivalent to g(f(x, y)) 20 | */ 21 | const pipeline = (...args) => (...fns) => { 22 | const f1 = fns[0] 23 | return fns.slice(1).reduce( 24 | (acc, fn) => fn(acc), 25 | f1(...args) 26 | ) 27 | } 28 | 29 | 30 | 31 | // Helper to inherit the prototype 32 | const inheritState = (target, source) => 33 | pipeline(source)( 34 | Object.getPrototypeOf, 35 | prototype => Object.setPrototypeOf(target, prototype) 36 | ) 37 | 38 | 39 | 40 | /* ----- CPS operators ----- */ 41 | 42 | /** 43 | * Create CPS function with given tuple as immediate output 44 | * 45 | * @name CPS.of 46 | * @params {Tuple} (...args) tuple of arbitrary values 47 | * @returns {CPS Function} CPS.of(...args) 48 | * that outputs (...args) inside its first callback 49 | * no other output is passed to any other callback 50 | * 51 | * # Example 52 | * 53 | * CPS.of(x1, x2, x3) 54 | * is equivalent to the CPS function 55 | * cb => cb(x1, x2, x3) 56 | * 57 | * The pair (CPS.map, CPS.of) conforms to the Pointed Functor spec, 58 | * see {@link https://stackoverflow.com/a/41816326/1614973} 59 | */ 60 | const of = (...args) => cb => cb(...args) 61 | 62 | 63 | /** 64 | * Map CPS function over arbitrary tuple of functions, where for each n, 65 | * the nth function from the tuple transforms the output of the nth callback 66 | * 67 | * @signature (...fns) -> CPS -> CPS (curried) 68 | * 69 | * @name CPS.map 70 | * @params {Tuple of Functions} (...fns) 71 | * @curriedParam {CPS Function} cpsFun 72 | * @returns {CPS Function} CPS.map(...fns)(cpsFun) 73 | * whose nth callback's output equals 74 | * the nth callback's output of `cpsFun` transformed via function fns[n] 75 | * 76 | * # Examples 77 | * 78 | * const cpsFun = (cb1, cb2) => cb1(2, 3) + cb2(7) 79 | * 2 callbacks receive corresponding outputs (2, 3) and (7) 80 | * CPS.map(f1, f2)(cpsFun) 81 | * is equivalent to the CPS function 82 | * (cb1, cb2) => cb1(f1(2, 3)) + cb2(f2(7)) 83 | * where f1 and f2 transform respective outputs 84 | * 85 | * const cpsFromPromise = promise => (onRes, onErr) => promise.then(onRes, onErr) 86 | * CPS.map(f1, f2)(cpsFromPromise(promise)) 87 | * is equivalent to 88 | * cpsFromPromise(promise.then(f1).catch(f2)) 89 | * 90 | * The pair (CPS.map, CPS.of) conforms to the Pointed Functor spec, 91 | * see {@link https://stackoverflow.com/a/41816326/1614973} 92 | */ 93 | const map = (...fns) => cpsFun => { 94 | let cpsNew = (...cbs) => cpsFun( 95 | // precompose every callback with fn matched by index or pass directly the args 96 | // collect functions in array and pass as callbacks to cpsFun 97 | ...cbs.map( 98 | (cb, idx) => (...args) => fns[idx] ? cb(fns[idx](...args)) : cb(...args) 99 | ) 100 | ) 101 | inheritState(cpsNew, cpsFun) 102 | return cpsNew 103 | } 104 | 105 | 106 | /** 107 | * Chains outputs of CPS function with arbitrary tuple of other CPS functions, 108 | * where the nth function applies to each output of the nth callback 109 | * and the resulting outputs are gathered by index 110 | * 111 | * @signature (...cpsFns) -> CPS -> CPS (curried) 112 | * 113 | * @name CPS.chain 114 | * @params {Tuple of CPS Functions} (...cpsFns) 115 | * @curriedParam {CPS Function} cpsFun 116 | * @returns {CPS Function} CPS.chain(...fns)(cpsFun) 117 | * whose nth callback's output is gathered from 118 | * the nth callbacks' outputs of each function fns[j] for each j 119 | * evaluated for each output of the jth callback of `cpsFun` 120 | * 121 | * # Example 122 | * 123 | * const cpsFun = (cb1, cb2) => cb1(2, 3) + cb2(7, 9) 124 | * 2 callbacks receive outputs (2, 3) and (7, 9) 125 | * const cpsF1 = (x, y) => (cb1, cb2) => cb1(x + y) + cb2(x - y) 126 | * const cpsF2 = (x, y) => cb => cb(x, -y) 127 | * CPS.chain(cpsF1, cpsF2)(cpsFun) 128 | * is equivalent to the CPS function 129 | * (cb1, cb2) => cb1(5) + cb2(7, -9) 130 | */ 131 | const chain = (...cpsFns) => cpsFun => { 132 | let cpsNew = (...cbs) => cpsFun( 133 | // precompose every callback with fn matched by index or pass directly the args, 134 | // collect functions in array and pass as callbacks to cpsFun 135 | ...cpsFns.map( 136 | // all callbacks from the chain get passed to each cpsFn 137 | cpsFn => (...args) => cpsFn(...args)(...cbs) 138 | ) 139 | ) 140 | inheritState(cpsNew, cpsFun) 141 | return cpsNew 142 | } 143 | 144 | 145 | // pass through only input truthy `pred` 146 | // const cpsFilter = pred => (...input) => cb => { 147 | // if (pred(...input)) cb(...input) 148 | // } 149 | 150 | // call `chain` with the list of arguments, one per each predicate 151 | const filter = (...preds) => 152 | chain(...preds.map((pred, idx) => 153 | (...input) => (...cbs) => { 154 | if (pred(...input)) cbs[idx](...input) 155 | } 156 | )) 157 | 158 | 159 | /** 160 | * Iterates tuple of reducers over tuple of states 161 | * and outputs from CPS function regarded as actions. 162 | * `reducers` and `states` are matched by index. 163 | * 164 | * @signature (...reducers) -> (...states) -> cpsAction -> cpsState 165 | * 166 | * @name CPS.scan 167 | * @params {Tuple of functions} (...reducers) 168 | * where @signature of each reducer is (state, ...actions) -> state 169 | * @params {Tuple of values} (...states) 170 | * @param {CPS function} cpsAction 171 | * @returns {CPS function} CPS.scan(...reducers)(...initStates)(cpsAction) 172 | * whose nth callback receives the outputs obtained by iterating 173 | * the stream of outputs from the nth callback of cpsAction 174 | * over reducers[n] starting from with initStates[n] 175 | * 176 | */ 177 | const scan = (...reducers) => (...initStates) => { 178 | let states = initStates 179 | // chain cpsAction with tuple of CPS function 180 | return cpsAction => pipeline(cpsAction)( 181 | // chaining outputs of cpsAction with multiple reducers, one per state 182 | chain( 183 | // chain receives tuple of functions, one per reducer 184 | ...reducers.map((reducer, idx) => 185 | // nth CPS function inside chain receives nth callback output of cpsAction 186 | (...action) => (...cbs) => { 187 | // accessing states and reducers by index 188 | // (undefined === states[idx]) && (states[idx] = initStates[idx]) 189 | states[idx] = reducer(states[idx], ...action) 190 | cbs[idx]( states[idx] ) 191 | } 192 | ) 193 | )) 194 | } 195 | 196 | 197 | 198 | 199 | /* ----- CPS methods ----- */ 200 | 201 | const apply2this = fn => 202 | function(...args) {return fn(...args)(this)} 203 | 204 | // apply function to all values of object 205 | const objMap = fn => obj => 206 | Object.keys(obj).reduce((acc, key) => { 207 | acc[key] = fn(obj[key]) 208 | return acc 209 | }, {}) 210 | 211 | // Prototype methods 212 | const protoObj = objMap(apply2this)({ 213 | map, 214 | chain 215 | }) 216 | 217 | const CPS = cpsFn => { 218 | // clone the function 219 | let cpsWrapped = (...args) => cpsFn(...args) 220 | Object.setPrototypeOf(cpsWrapped, protoObj) 221 | return cpsWrapped 222 | } 223 | 224 | module.exports = { 225 | pipeline, of, map, chain, filter, scan, CPS 226 | } 227 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.8.4", 3 | "name": "tiny-cps", 4 | "description": "Tiny goodies for Continuation-Passing-Style functions", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js" 8 | ], 9 | "repository": "https://github.com/dmitriz/tiny-cps.git", 10 | "author": "Dmitri Zaitsev (https://github.com/dmitriz)", 11 | "license": "MIT", 12 | "scripts": { 13 | "start": "npm version", 14 | "preversion": "npm test", 15 | "postversion": "npm publish", 16 | "postpublish": "git pull --rebase && git push origin --all; git push origin --tags", 17 | "test": "nyc ava test/*test.js", 18 | "watch": "nyc ava test/*test.js -w", 19 | "tape": "tape test/*test.js", 20 | "tap": "tap test/*test.js", 21 | "sync": "git ac; git pull --rebase && git push", 22 | "cov": "nyc report --reporter=text-lcov | coveralls", 23 | "codecov": "nyc report --reporter=text-lcov > coverage.lcov && codecov", 24 | "toc": "markdown-toc README.md > toc" 25 | }, 26 | "keywords": [ 27 | "continuation", 28 | "functional", 29 | "monad", 30 | "applicative", 31 | "functor", 32 | "stream", 33 | "promise", 34 | "async" 35 | ], 36 | "devDependencies": { 37 | "ava": "3.4.0", 38 | "codecov": "3.6.5", 39 | "coveralls": "3.0.9", 40 | "nyc": "14.1.1", 41 | "tap": "14.10.6", 42 | "tape": "4.13.0" 43 | }, 44 | "dependencies": {} 45 | } 46 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/chain.test.js: -------------------------------------------------------------------------------- 1 | const test = require('./config').test 2 | const { chain } = require('..') 3 | 4 | test('chain over single CPS function', t => { 5 | const cpsFun = cb => cb(42) 6 | const cpsNew = chain(x => cb => cb(x*2))(cpsFun) 7 | // 84 passed as output into the first callback 8 | cpsNew(t.cis(84)) 9 | }) 10 | 11 | test('chain over single CPS function with several arguments', t => { 12 | // (5, 2) passed as output into the first callback 13 | const cpsFun = cb => cb(5, 2) 14 | const cpsNew = chain( 15 | (a, b) => cb => cb(a - b) 16 | )(cpsFun) 17 | // new output is 3 = 5 - 2 18 | cpsNew(t.cis(3)) 19 | }) 20 | 21 | test('all repeated outputs are passed', t => { 22 | const cpsFun = cb => { cb(42); cb(42) } 23 | const cpsNew = chain(x => cb => cb(x + 1))(cpsFun) 24 | // the callback t.cis(43) must be executed twice 25 | t.plan(2) 26 | cpsNew(t.cis(43)) 27 | }) 28 | 29 | test('also all repeated outputs passed from transforming functions', t => { 30 | const cpsFun = cb => { cb(42) } 31 | const cpsNew = chain( 32 | // cb is called twice - 2 outputs 33 | x => cb => cb(x + 1) + cb(x + 1) 34 | )(cpsFun) 35 | // the callback t.cis(43) must be executed twice 36 | t.plan(2) 37 | cpsNew(t.cis(43)) 38 | }) 39 | 40 | test('chain over single function with no arguments', t => { 41 | // empty tuple as output 42 | const cpsFun = cb => cb() 43 | // transform empty tuple into 30 as output 44 | const cpsNew = chain( 45 | () => cb => cb(30) 46 | )(cpsFun) 47 | // new output is 30 48 | cpsNew(t.cis(30)) 49 | }) 50 | 51 | test('all callbacks passed when chain with single function', t => { 52 | // 42 is passed as output 53 | const cpsFun = cb => cb(42) 54 | const cpsNew = chain( 55 | x => (cb1, cb2) => { 56 | cb1(x * 2) 57 | cb2(x + 10) 58 | })(cpsFun) 59 | // 84 passed into the first, and 52 into the second callback 60 | t.plan(2) 61 | cpsNew(t.cis(84), t.cis(52)) 62 | }) 63 | 64 | test('chain over multiple functions with the same output twice', t => { 65 | // 42 and 10.5 passed respectively into the first and second callback 66 | const cpsFun = (cb1, cb2) => cb1(42) + cb2(10.5) 67 | // both output are transformed into the same result 68 | const cpsNew = chain( 69 | x => cb => cb(x/2), 70 | x => cb => cb(x*2) 71 | )(cpsFun) 72 | t.plan(2) 73 | cpsNew(t.cis(21)) 74 | }) 75 | 76 | test('chain over multiple functions merges the outputs', t => { 77 | // one output for each callback 78 | let called = false 79 | const cpsFun = (cb1, cb2) => { cb1(2); cb2(5) } 80 | const newCps = chain( 81 | // output 42 is passed here 82 | x => cb => cb(x/2), 83 | // output 21 is passed here 84 | x => cb => cb(x*2) 85 | )(cpsFun) 86 | 87 | // called twice - with 21 and 42 as outputs 88 | t.plan(2) 89 | newCps(res => { 90 | t.cis(called ? 10 : 1)(res) 91 | called = true 92 | }) 93 | }) 94 | 95 | test('multiple callbacks from transforming functions merge by index', t => { 96 | const cpsFun = (cb1, cb2) => { cb1(8); cb2(2) } 97 | const newCps = chain( 98 | // output 8 is passed here as x 99 | x => (c1, c2) => c1(x/2) + c2(x), 100 | // output 2 is passed here as x 101 | x => (cb1, cb2) => cb1(x*2) + cb2(x*4), 102 | )(cpsFun) 103 | 104 | // each callback is called twice 105 | t.plan(4) 106 | newCps( 107 | // 4 = 8/2 = 2*2 is passed twice, once from each of c1 and cb1 108 | t.cis(4), 109 | // 8 = 8 = 2*4 is passed twice, once from each of c2 and cb2 110 | t.cis(8) 111 | ) 112 | }) 113 | -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test: require('./helpers/ava-patched'), 3 | // test: require('./helpers/tape-patched'), 4 | } 5 | -------------------------------------------------------------------------------- /test/cps.test.js: -------------------------------------------------------------------------------- 1 | const test = require('./config').test 2 | const { CPS } = require('..') 3 | 4 | const cpsFun = cb => cb(42) 5 | const cpsWrapped = CPS(cpsFun) 6 | 7 | 8 | test('does not mutate source', t => { 9 | let obj = {} 10 | CPS(obj) 11 | t.deepEqual(obj, {}) 12 | }) 13 | 14 | test('map method works correctly', t => { 15 | const cpsNew = cpsWrapped.map(y => y + 1) 16 | cpsNew(t.cis(43)) 17 | }) 18 | 19 | test('map is provided on target as well', t => { 20 | cpsWrapped 21 | .map(z => z + 2) 22 | .map(z => z - 1) 23 | (t.cis(43)) 24 | }) 25 | 26 | test('provide chain method on CPS functions', t => { 27 | CPS(cpsFun).chain(y => cb => cb(y + 1)) 28 | (t.cis(43)) 29 | }) 30 | test('chain is provided on target as well', t => { 31 | cpsWrapped 32 | .chain(z => cb => cb(z + 2)) 33 | .chain(z => cb => cb(z - 1)) 34 | (t.cis(43)) 35 | }) 36 | 37 | test('function on CPS(fn) is delegated to fn', t => { 38 | const cpsFun = cb => cb(42) 39 | t.is( 40 | CPS(cpsFun)(x => x + 1), 41 | 43 42 | ) 43 | }) 44 | -------------------------------------------------------------------------------- /test/filter.test.js: -------------------------------------------------------------------------------- 1 | const test = require('./config').test 2 | const { filter } = require('..') 3 | 4 | test('filter single output', t => { 5 | const cpsFun = cb => cb(42) + cb(33) 6 | t.plan(1) 7 | filter(n => n > 35)(cpsFun)(t.cis(42)) 8 | }) 9 | 10 | test('filter out all outputs', t => { 11 | const cpsFun = cb => cb(42) + cb(33) 12 | t.plan(0) 13 | // won't call the callback 14 | filter(n => n > 45)(cpsFun)() 15 | }) 16 | 17 | test('filter multiple occurences', t => { 18 | const cpsFun = cb => cb(33) + cb(42) + cb(33) 19 | t.plan(2) 20 | filter(n => n < 35)(cpsFun)(t.cis(33)) 21 | }) 22 | 23 | test('filter tuples', t => { 24 | const cpsFun = cb => cb(1,2) + cb(3,4) 25 | t.plan(1) 26 | const cpsNew = filter(a => a > 1)(cpsFun) 27 | cpsNew(t.cis(3)) 28 | }) 29 | 30 | test('filter multiple outputs', t => { 31 | const cpsFun = (c1, c2) => 32 | c1(1) + c1(2) + 33 | c2(3) + c2(4) 34 | const cpsNew = filter( 35 | // filters 1st output [1,2] 36 | n => n < 2, 37 | // filters 2nd output [3,4] 38 | n => n < 1 39 | )(cpsFun) 40 | t.plan(1) 41 | cpsNew(t.cis(1)) 42 | }) 43 | 44 | test('filter 3 outputs', t => { 45 | const cpsFun = (c1, c2) => 46 | c1(2) + c2(5) 47 | const cpsNew = filter( 48 | // filters 1st output [1,2] 49 | n => n < 4, 50 | // filters 2nd output [3,4] 51 | n => n < 6 52 | )(cpsFun) 53 | t.plan(2) 54 | cpsNew(t.cis(2), t.cis(5)) 55 | }) 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /test/helpers/ava-patched.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | 3 | /* 4 | Curried version of the `.is` assertion: patching test object with `.cis` prop to delegate to its `.is` prop 5 | https://github.com/avajs/ava/blob/master/docs/03-assertions.md#isvalue-expected-message 6 | 7 | Usage: t.cis(a)(b) instead of t.is(a, b) 8 | */ 9 | module.exports = (title, fn) => 10 | test(title, t => { 11 | t.cis = a => b => t.is(a, b) 12 | return fn(t) 13 | }) 14 | -------------------------------------------------------------------------------- /test/helpers/ava-patched.test.js: -------------------------------------------------------------------------------- 1 | const test = require('./ava-patched') 2 | 3 | test('test equality as curried function', t => { 4 | t.cis(2)(2) 5 | }) 6 | -------------------------------------------------------------------------------- /test/helpers/tape-patched.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | // const test = require('tap').test 3 | 4 | /* 5 | Curried version of the `.is` assertion: patching test object with `.cis` prop to delegate to its `.is` prop 6 | 7 | Usage: t.cis(a)(b) 8 | */ 9 | module.exports = (title, fn) => 10 | test(title, t => { 11 | t.cis = a => b => t.is(a, b) 12 | // run consumer test function 13 | fn(t) 14 | // tape requires to end each test 15 | t.end() 16 | }) 17 | -------------------------------------------------------------------------------- /test/helpers/tape-patched.test.js: -------------------------------------------------------------------------------- 1 | const test = require('./tape-patched') 2 | 3 | test('test equality as curried function', t => { 4 | t.cis(2)(2) 5 | }) 6 | -------------------------------------------------------------------------------- /test/map.test.js: -------------------------------------------------------------------------------- 1 | const test = require('./config').test 2 | const { map } = require('..') 3 | 4 | test('map over single function', t => { 5 | const cpsFun = cb => cb(42) 6 | map(x => x*2)(cpsFun)(t.cis(84)) 7 | }) 8 | 9 | test('map over single function with several arguments', t => { 10 | const cpsFun = cb => cb(42, 24) 11 | map((a, b) => a - b)(cpsFun)(t.cis(18)) 12 | }) 13 | 14 | test('map over single function with no arguments', t => { 15 | const cpsFun = cb => cb() 16 | map(() => 30)(cpsFun)(t.cis(30)) 17 | }) 18 | 19 | test('further callbacks are unaffected when map over single function', t => { 20 | const cpsFun = (cb1, cb2) => {cb1(42); cb2(23)} 21 | map(x => x*2)(cpsFun)(x=>x, t.cis(23)) 22 | }) 23 | 24 | test('map over multiple functions', t => { 25 | const cpsFun = (cb1, cb2) => {cb1(42); cb2(23)} 26 | map(x => x/2, x => x*2)(cpsFun)(t.cis(21), t.cis(46)) 27 | }) 28 | 29 | test('map over more functions than callbacks, the extra functions are ignored', t => { 30 | const cpsFun = cb => cb(42) 31 | map(x => x*2, x => x+10)(cpsFun)(t.cis(84)) 32 | }) 33 | -------------------------------------------------------------------------------- /test/of.test.js: -------------------------------------------------------------------------------- 1 | const test = require('./config').test 2 | const { of } = require('..') 3 | 4 | test('create CPS functions with single output', t => { 5 | of(42)(t.cis(42)) 6 | }) 7 | 8 | test('create CPS with multi-arg output', t => { 9 | // callback to test that x + y = 9 10 | const callback = (x, y) => t.cis(9)(x + y) 11 | of(2, 7)(callback) 12 | }) 13 | 14 | test('no output is passed to the 2nd and following callbacks', t => { 15 | t.plan(0) //no test will be run 16 | of(55)(x=>x, t.cis(42)) 17 | of(3,2,1)(x=>x, t.cis(42)) 18 | }) -------------------------------------------------------------------------------- /test/pipeline.test.js: -------------------------------------------------------------------------------- 1 | const test = require('./config').test 2 | const { pipeline } = require('..') 3 | 4 | test('pass single argument to function', t => { 5 | t.is(pipeline(1)(x => x + 2), 3) 6 | }) 7 | 8 | test('pass multiple arguments to function', t => { 9 | t.is(pipeline(1, 5)( 10 | (x, y) => x + y 11 | ), 6) 12 | t.is(pipeline(1, 2, 5)( 13 | (x, y, z) => x + y - z 14 | ), -2) 15 | }) 16 | 17 | test('chain multiple functions', t => { 18 | t.is(pipeline(2)( 19 | x => x + 1, 20 | y => y - 2 21 | ), 1) 22 | t.is(pipeline(2)( 23 | x => x + 1, 24 | y => y - 2, 25 | z => z * 2 26 | ), 2) 27 | }) 28 | 29 | test('chain multiple functions with multiple args', t => { 30 | t.is(pipeline(1, 3)( 31 | (x, y) => x + y, 32 | z => z * 2 33 | ), 8) 34 | }) 35 | -------------------------------------------------------------------------------- /test/preserve-state.test.js: -------------------------------------------------------------------------------- 1 | const test = require('./config').test 2 | const { map, chain } = require('..') 3 | 4 | 5 | // --- Preserving state 6 | 7 | const checkState = (name, cpsFn, cpsFn1) => { 8 | test(name + 'prototype passed to transformed CPS fn', t => { 9 | t.is(cpsFn1.a, 22) 10 | }) 11 | } 12 | 13 | const cpsFn = cb => cb(42) 14 | const protoObj = {a: 22} 15 | Object.setPrototypeOf(cpsFn, protoObj) 16 | 17 | const cpsFn1 = map(x => x*2)(cpsFn) 18 | const cpsFn2 = chain(x => cb => cb(x*2))(cpsFn) 19 | 20 | checkState('map: ', cpsFn, cpsFn1) 21 | checkState('chain: ', cpsFn, cpsFn2) 22 | -------------------------------------------------------------------------------- /test/scan.test.js: -------------------------------------------------------------------------------- 1 | const test = require('./config').test 2 | const { scan } = require('..') 3 | 4 | test('scan over single callback output', t => { 5 | const reducer = (acc, x) => acc + x 6 | const initState = 10 7 | const cpsFun = cb => cb(42) 8 | t.plan(1) 9 | scan(reducer)(initState)(cpsFun)(t.cis(52)) 10 | }) 11 | 12 | test('scan over single repeated callback output', t => { 13 | let called = false 14 | const reducer = (acc, x) => acc + x 15 | const initState = 10 16 | const cpsFun = cb => { cb(2); cb(8) } 17 | const newCps = scan(reducer)(initState)(cpsFun) 18 | 19 | // called twice with 20 | // 12 = 10 + 2 and 20 = 10 + 2 + 8 as outputs 21 | t.plan(2) 22 | newCps(res => { 23 | t.cis(called ? 20 : 12)(res) 24 | called = true 25 | }) 26 | }) 27 | 28 | test('scan over outputs from 2 callbacks', t => { 29 | const r = (acc, x) => acc + x 30 | const cpsFun = (cb1, cb2) => cb1(2) + cb2(3) 31 | const newCps = scan(r, r)(10, 11)(cpsFun) 32 | 33 | // called with 12 = 10 + 2 and 14 = 11 + 3 34 | t.plan(2) 35 | newCps(t.cis(12), t.cis(14)) 36 | }) 37 | -------------------------------------------------------------------------------- /toc: -------------------------------------------------------------------------------- 1 | - [tiny-cps](#tiny-cps) 2 | - [CPS functions](#cps-functions) 3 | * [Why?](#why) 4 | * [Advanced composability](#advanced-composability) 5 | * [What is new here?](#what-is-new-here) 6 | + [Variadic input and output](#variadic-input-and-output) 7 | + [Full power of multiple outputs streams](#full-power-of-multiple-outputs-streams) 8 | + [Functional progamming paradigm](#functional-progamming-paradigm) 9 | + [Lazy or eager?](#lazy-or-eager) 10 | + [Differences with Haskell](#differences-with-haskell) 11 | + ["Do less" is a feature](#do-less-is-a-feature) 12 | * [Terminology](#terminology) 13 | * [Using CPS functions](#using-cps-functions) 14 | * [What about Callback Hell?](#what-about-callback-hell) 15 | * [Asynchronous iteration over array](#asynchronous-iteration-over-array) 16 | - [Examples of CPS functions](#examples-of-cps-functions) 17 | * [Promise producers](#promise-producers) 18 | * [Promises](#promises) 19 | * [Node API](#node-api) 20 | * [HTTP requests](#http-requests) 21 | * [Database Access](#database-access) 22 | * [Middleware e.g. in Express or Redux](#middleware-eg-in-express-or-redux) 23 | * [Web Sockets](#web-sockets) 24 | * [Stream libraries](#stream-libraries) 25 | + [Pull Streams](#pull-streams) 26 | + [Flyd](#flyd) 27 | * [Event aggregation](#event-aggregation) 28 | - [Comparison with Promises and Callbacks](#comparison-with-promises-and-callbacks) 29 | * [Returning results](#returning-results) 30 | * [Chaining](#chaining) 31 | * [Asynchronous composition](#asynchronous-composition) 32 | * [Error handling](#error-handling) 33 | * [Signatures](#signatures) 34 | * [Standardization](#standardization) 35 | - [Functional and Fluent API](#functional-and-fluent-api) 36 | * [Conventions](#conventions) 37 | * [CPS.map](#cpsmap) 38 | + [Mapping over single function](#mapping-over-single-function) 39 | + [Mapping over multiple functions](#mapping-over-multiple-functions) 40 | + [Map taking multiple arguments](#map-taking-multiple-arguments) 41 | + [Functor laws](#functor-laws) 42 | + [CPS.of](#cpsof) 43 | * [CPS.chain](#cpschain) 44 | + [Transforming multiple arguments into multiple arguments](#transforming-multiple-arguments-into-multiple-arguments) 45 | + [Why is it called `chain`?](#why-is-it-called-chain) 46 | + [Composing multiple outputs](#composing-multiple-outputs) 47 | + [Passing multiple CPS functions to `chain`](#passing-multiple-cps-functions-to-chain) 48 | + [Monadic laws](#monadic-laws) 49 | - [Associativity law](#associativity-law) 50 | - [Identity laws](#identity-laws) 51 | * [Application of `chain`: Turn Node API into Promise style callbacks](#application-of-chain-turn-node-api-into-promise-style-callbacks) 52 | * [CPS.ap](#cpsap) 53 | + [Running CPS functions in parallel](#running-cps-functions-in-parallel) 54 | + [Lifting functions of multiple parameters](#lifting-functions-of-multiple-parameters) 55 | - [Promise.all](#promiseall) 56 | - [Usage notes](#usage-notes) 57 | + [Applying multiple functions inside `ap`](#applying-multiple-functions-inside-ap) 58 | + [Applicative laws](#applicative-laws) 59 | * [CPS.merge](#cpsmerge) 60 | + [Relation with Promise.race](#relation-with-promiserace) 61 | + [Commutative Monoid](#commutative-monoid) 62 | * [CPS.filter](#cpsfilter) 63 | + [Filtering over multiple functions](#filtering-over-multiple-functions) 64 | + [Implementation via `chain`](#implementation-via-chain) 65 | * [CPS.scan](#cpsscan) --------------------------------------------------------------------------------