├── .gitignore ├── .npmignore ├── src ├── copyright-header.txt └── fpo.src.js ├── .travis.yml ├── tests ├── index.html └── qunit.config.js ├── scripts ├── node-tests.js └── build-core.js ├── docs ├── README.md ├── core-API.md └── std-API.md ├── LICENSE.txt ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .coveralls.yml 2 | node_modules/ 3 | dist/ 4 | coverage/ 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | .gitignore 3 | .coveralls.yml 4 | .travis.yml 5 | node_modules/ 6 | coverage/ 7 | -------------------------------------------------------------------------------- /src/copyright-header.txt: -------------------------------------------------------------------------------- 1 | /*! fpo.js 2 | v${version} (c) ${year} Kyle Simpson 3 | MIT License: http://getify.mit-license.org 4 | */ 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 6 5 | - 7 6 | env: 7 | - TEST_PACKAGE=true 8 | branches: 9 | only: 10 | - master 11 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FPO Test Suite 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /scripts/node-tests.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require("path"); 4 | 5 | /* istanbul ignore next */ 6 | if (process.env.TEST_DIST) { 7 | global.FPO = require(path.join("..","dist","fpo.js")); 8 | } 9 | /* istanbul ignore next */ 10 | else if (process.env.TEST_PACKAGE) { 11 | global.FPO = require(path.join("..")); 12 | } 13 | else { 14 | global.FPO = require(path.join("..","src","fpo.src.js")); 15 | } 16 | 17 | global.QUnit = require("qunitjs"); 18 | 19 | require("../tests/qunit.config.js"); 20 | require("../tests/tests.js"); 21 | 22 | QUnit.start(); 23 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # FPO.js Docs 2 | 3 | * See [Core API](core-API.md) for documentation on all the methods in the `FPO.*` namespace. 4 | * See [Standard API](std-API.md) for documenation on the methods in the `FPO.std.*` namespace. 5 | 6 | All core methods have a standard positional-parameter form available under the `FPO.std.*` namespace. In many respects, their conceptual behavior is the same, but in some cases there's some differences to be aware of. 7 | 8 | There are also a few methods on the `FPO.std.*` namespace that have no equivalent in the core API as they are unnecessary or don't make sense. 9 | 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Kyle Simpson 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fpo", 3 | "version": "6.0.1", 4 | "description": "FP library for JavaScript. Supports named-argument style methods.", 5 | "main": "./dist/fpo.js", 6 | "scripts": { 7 | "test": "node scripts/node-tests.js", 8 | "test:dist": "TEST_DIST=true npm test", 9 | "test:package": "TEST_PACKAGE=true npm test", 10 | "test:all": "npm test && npm run test:dist && npm run test:package", 11 | "coverage": "istanbul cover scripts/node-tests.js", 12 | "coverage:report": "istanbul cover scripts/node-tests.js --report lcovonly && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js", 13 | "build-core": "node scripts/build-core.js", 14 | "build": "npm run build-core", 15 | "prepare": "npm run build", 16 | "prepublish": "npm run build && npm run test:all", 17 | "publish": "npm run coverage:report" 18 | }, 19 | "devDependencies": { 20 | "coveralls": "~2.12.0", 21 | "qunitjs": "~2.1.1" 22 | }, 23 | "repository": "getify/fpo", 24 | "keywords": [ 25 | "fp", 26 | "functional programming" 27 | ], 28 | "bugs": { 29 | "url": "https://github.com/getify/fpo/issues", 30 | "email": "getify@gmail.com" 31 | }, 32 | "homepage": "https://github.com/getify/fpo", 33 | "author": "Kyle Simpson ", 34 | "license": "MIT" 35 | } 36 | -------------------------------------------------------------------------------- /scripts/build-core.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require("fs"), 4 | path = require("path"), 5 | // ugly = require("uglify-js"), 6 | packageJSON, 7 | copyrightHeader, 8 | version, 9 | year = (new Date()).getFullYear(), 10 | 11 | ROOT_DIR = path.join(__dirname,".."), 12 | SRC_DIR = path.join(ROOT_DIR,"src"), 13 | DIST_DIR = path.join(ROOT_DIR,"dist"), 14 | 15 | LIB_SRC = path.join(SRC_DIR,"fpo.src.js"), 16 | LIB_DIST = path.join(DIST_DIR,"fpo.js"), 17 | 18 | result 19 | ; 20 | 21 | console.log("*** Building Core ***"); 22 | console.log(`Building: ${LIB_DIST}`); 23 | 24 | try { 25 | // try to make the dist directory, if needed 26 | try { 27 | fs.mkdirSync(DIST_DIR,0o755); 28 | } 29 | catch (err) { } 30 | 31 | // NOTE: since uglify doesn't yet support ES6, no minifying happening :( 32 | result = fs.readFileSync(LIB_SRC,{ encoding: "utf8" }); 33 | 34 | // result = ugly.minify(path.join(SRC_DIR,"fpo.src.js"),{ 35 | // mangle: { 36 | // keep_fnames: true 37 | // }, 38 | // compress: { 39 | // keep_fnames: true 40 | // }, 41 | // output: { 42 | // comments: /^!/ 43 | // } 44 | // }); 45 | 46 | // read version number from package.json 47 | packageJSON = JSON.parse( 48 | fs.readFileSync( 49 | path.join(ROOT_DIR,"package.json"), 50 | { encoding: "utf8" } 51 | ) 52 | ); 53 | version = packageJSON.version; 54 | 55 | // read copyright-header text, render with version and year 56 | copyrightHeader = fs.readFileSync( 57 | path.join(SRC_DIR,"copyright-header.txt"), 58 | { encoding: "utf8" } 59 | ).replace(/`/g,""); 60 | copyrightHeader = Function("version","year",`return \`${copyrightHeader}\`;`)( version, year ); 61 | 62 | // append copyright-header text 63 | result = copyrightHeader + result; 64 | 65 | // write dist 66 | fs.writeFileSync( LIB_DIST, result /* result.code + "\n" */, { encoding: "utf8" } ); 67 | 68 | console.log("Complete."); 69 | } 70 | catch (err) { 71 | console.error(err); 72 | process.exit(1); 73 | } 74 | -------------------------------------------------------------------------------- /tests/qunit.config.js: -------------------------------------------------------------------------------- 1 | QUnit.config.requireExpects = true; 2 | 3 | QUnit.begin(begin); 4 | QUnit.log(testLog); 5 | QUnit.testDone(testDone); 6 | QUnit.done(done); 7 | 8 | var testLogEntries = {}; 9 | 10 | // ****************************** 11 | 12 | function begin(details){ 13 | printEnvNotification(); 14 | 15 | if (details.totalTests > 0) { 16 | console.log(`FPO Test Suite (${details.totalTests})`); 17 | console.log(""); 18 | } 19 | else { 20 | console.log(`FPO Test Suite: empty!`); 21 | process.exit(1); 22 | } 23 | } 24 | 25 | function testLog(details) { 26 | var testId = details.testId; 27 | 28 | testLogEntries[testId] = testLogEntries[testId] || {}; 29 | testLogEntries[testId][details.message] = details; 30 | } 31 | 32 | function testDone(results){ 33 | var testId = results.testId; 34 | 35 | if (results.failed > 0) { 36 | console.log(`Failed: '${results.name}' (${results.failed}/${results.total})`); 37 | for (let i = 0; i < results.assertions.length; i++) { 38 | if (results.assertions[i].result === false) { 39 | let { message, expected, actual } = testLogEntries[testId][results.assertions[i].message]; 40 | console.log(` ${message}`); 41 | console.log(` expected: ${prettyPrint(expected)}`); 42 | console.log(` actual: ${prettyPrint(actual)}`); 43 | } 44 | } 45 | } 46 | else if (results.passed > 0) { 47 | console.log(`Passed: '${results.name}' (${results.passed}/${results.total})`); 48 | } 49 | else { 50 | console.log(`No assertions run: '${results.name}'`); 51 | } 52 | } 53 | 54 | function done(results){ 55 | console.log(""); 56 | 57 | if (results.failed > 0) { 58 | console.log(`Failed (${results.failed}/${results.total})`); 59 | printEnvNotification(); 60 | process.exit(1); 61 | } 62 | else if (results.passed > 0) { 63 | console.log(`Passed (${results.passed}/${results.total})`); 64 | printEnvNotification(); 65 | process.exit(0); 66 | } 67 | else { 68 | console.log("No tests run!"); 69 | printEnvNotification(); 70 | process.exit(1); 71 | } 72 | } 73 | 74 | function prettyPrint(v) { 75 | if (Array.isArray(v)) { 76 | return `[${ v.map( prettyPrint ).toString() }]`; 77 | } 78 | else if (v && typeof v == "object") { 79 | return JSON.stringify(v,function(k,v){ 80 | if (v === undefined) { 81 | return null; 82 | } 83 | return v; 84 | }); 85 | } 86 | return String(v); 87 | } 88 | 89 | function printEnvNotification() { 90 | console.log(""); 91 | console.log("**********************************"); 92 | if (process.env.TEST_DIST) { 93 | console.log("********** TESTING DIST **********"); 94 | } 95 | else if (process.env.TEST_PACKAGE) { 96 | console.log("******** TESTING PACKAGE *********"); 97 | } 98 | else { 99 | console.log("********** TESTING SRC ***********"); 100 | } 101 | console.log("**********************************"); 102 | console.log(""); 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FPO.js 2 | 3 | [![Build Status](https://travis-ci.org/getify/fpo.svg?branch=master)](https://travis-ci.org/getify/fpo) 4 | [![npm Module](https://badge.fury.io/js/fpo.svg)](https://www.npmjs.org/package/fpo) 5 | [![Dependencies](https://david-dm.org/getify/fpo.svg)](https://david-dm.org/getify/fpo) 6 | [![devDependencies](https://david-dm.org/getify/fpo/dev-status.svg)](https://david-dm.org/getify/fpo) 7 | [![Coverage Status](https://coveralls.io/repos/github/getify/fpo/badge.svg?branch=master)](https://coveralls.io/github/getify/fpo?branch=master) 8 | 9 | FPO (/ˈefpō/) is an FP Library for JavaScript. The main aesthetic difference is that the [`FPO.*` core API methods](docs/core-API.md) are all styled to use named-arguments (object parameter destructuring) instead of individual positional arguments. 10 | 11 | ```js 12 | // positional arguments 13 | foo( 1, 2, 3 ); 14 | 15 | // named arguments 16 | foo({ z: 3, x: 1, y: 2 }); 17 | ``` 18 | 19 | Not only do named-arguments eliminate having to remember a method signature's parameter order -- named arguments can be provided in any order! -- they also make skipping optional parameters (to apply defaults) simple. 20 | 21 | This elimination of ordering concern and/or skipping arguments particularly comes in handy when you're currying. You don't have to juggle the parameter order at all; just pass in whichever named argument(s) you want, in whatever sequence you need! 22 | 23 | The other benefit is that these API methods will automatically work with your program's named-argument style functions. If you need to interoperate between both styles of function parameters in your program, adapt either style to the other using the [`FPO.apply(..)`](docs/core-API.md#fpoapply) and [`FPO.unapply(..)`](docs/core-API.md#fpounapply) methods. 24 | 25 | For convenience and familiarity sake, FPO also exposes all its methods in the traditional positional argument form, under [`FPO.std.*`](docs/std-API.md). These methods are very similar to their equivalents in Ramda, for example. 26 | 27 | **Note:** If you're not fully confident in your FP skills, I've written a book on FP in JavaScript: [Functional-Light JavaScript](https://github.com/getify/functional-light-js). Go check it out! 28 | 29 | ## Environment Support 30 | 31 | This library uses ES6+ features. If you need to support ES<=5 environments, transpile it first (with Babel, etc). 32 | 33 | ## At A Glance 34 | 35 | ```js 36 | // the classic/traditional method style 37 | // (on the `FPO.std.*` namespace) 38 | FPO.std.reduce( 39 | (acc,v) => acc + v, 40 | undefined, 41 | [3,7,9] 42 | ); // 19 43 | 44 | // FPO named-argument method style 45 | FPO.reduce({ 46 | arr: [3,7,9], 47 | fn: ({acc,v}) => acc + v 48 | }); // 19 49 | ``` 50 | 51 | Instead of needing to provide an `undefined` placeholder in the second argument position to skip specifying an initial value, named-argument style allows us to just omit that argument. We also specified `arr` first and `fn` second just to show order doesn't matter anymore! 52 | 53 | As with most FP libraries, all public FPO methods are curried. Currying with named-arguments (in any sequence!) is a breeze: 54 | 55 | ```js 56 | var f = FPO.reduce({ arr: [3,7,9] }); 57 | 58 | // later: 59 | f( {fn: ({acc,v}) => acc + v} ); // 19 60 | f( {fn: ({acc,v}) => acc * v} ); // 189 61 | ``` 62 | 63 | The equivalent using a standard FP library would look like: 64 | 65 | ```js 66 | var f = curry( flip( partialRight( reduce, [[3,7,9]] ) ) )( 0 ); 67 | 68 | f( (acc,v) => acc + v ); // 19 69 | ``` 70 | 71 | Phew, that's a lot of argument juggling! FPO eliminates all that noisy distraction. 72 | 73 | ## API 74 | 75 | * See [Core API](docs/core-API.md) for documentation on all the methods in the `FPO.*` namespace. 76 | * See [Standard API](docs/std-API.md) for documenation on the methods in the `FPO.std.*` namespace. 77 | 78 | All core methods have a standard positional-parameter form available under the `FPO.std.*` namespace. In many respects, their conceptual behavior is the same, but in some cases there's some differences to be aware of. 79 | 80 | There are also a few methods on the `FPO.std.*` namespace that have no equivalent in the core API as they are unnecessary or don't make sense. 81 | 82 | ## Adapting 83 | 84 | What if you have a traditional parameter-style function you want to use with one of the object-parameter style FPO API methods? Adapt it using the object-parameter-aware [`FPO.apply(..)`](docs/core-API.md#fpoapply): 85 | 86 | ```js 87 | // traditional-parameter style function 88 | function add(acc,v) { return acc + v; } 89 | 90 | FPO.reduce({ 91 | arr: [3,7,9], 92 | fn: FPO.apply( {fn: add} ) 93 | }); // 19 94 | ``` 95 | 96 | Adapting isn't limited to just interoperating with FPO methods. You can use [`FPO.apply(..)`](docs/core-API.md#fpoapply) and [`FPO.unapply(..)`](docs/core-API.md#fpounapply) to seamlessly adapt between functions of both styles in your own application code: 97 | 98 | ```js 99 | // note: cb is expected to be an "error-first" style 100 | // traditional callback function 101 | function ajax(url,cb) { .. } 102 | 103 | // our object-parameter style function 104 | function onResponse({ resp, err }) { 105 | if (!err) { 106 | console.log( resp ); 107 | } 108 | } 109 | 110 | // adapt `ajax(..)` to accept named arguments 111 | var request = FPO.apply( {fn: ajax} ); 112 | 113 | // now we can provide arguments in any order! 114 | request({ 115 | cb: 116 | // adapt the object-parameter style `onResponse(..)` 117 | // to work as a standard positional-argument function, as 118 | // expected by `ajax(..)` 119 | FPO.unapply( {fn: onResponse, props:["err","resp"]} ), 120 | 121 | url: "http://some.url" 122 | }); 123 | ``` 124 | 125 | ### Remapping Function Parameters/Arguments 126 | 127 | What if you have a function that expects a certain named parameter, but it needs to accept a differently named argument? For example: 128 | 129 | ```js 130 | function uppercase({ str }) { return str.toUpperCase(); } 131 | 132 | FPO.map( {fn: uppercase, arr: ["hello","world"]} ); 133 | // TypeError (`str` is undefined) 134 | ``` 135 | 136 | The problem here is that [`FPO.map(..)`](docs/core-API.md#fpomap) expects to call its mapper function with a `v` named argument, but `uppercase(..)` expects `str`. 137 | 138 | [`FPO.remap(..)`](docs/core-API.md#fporemap) -- despite the name similarity, no particular relationship to [`FPO.map(..)`](docs/core-API.md#fpomap), except that it's our example -- lets you adapt a function to remap its expected named parameters: 139 | 140 | ```js 141 | function uppercase({ str }) { return str.toUpperCase(); } 142 | 143 | FPO.map( { 144 | fn: FPO.remap( {fn: uppercase, args: {str: "v"}} ), 145 | arr: ["hello","world"] 146 | } ); 147 | // ["HELLO","WORLD"] 148 | ``` 149 | 150 | **Note:** The [`FPO.remap(..)`](docs/core-API.md#fporemap) utility passes through any non-remapped arguments as-is. 151 | 152 | ## Not Order, But Names 153 | 154 | The exchange we make for not needing to remember or juggle argument order is that we need to know/remember the parameter names. For example, [`FPO.reduce(..)`](docs/core-API.md#fporeduce) expects named arguments of `fn`, `v`, and `arr`. If you don't use those names, it won't work correctly. 155 | 156 | To aid in getting used to that tradeoff in usability, FPO uses straightforward conventions for parameter names; once learned, it should be mostly trivial to use any of the API methods. 157 | 158 | The named argument naming conventions (in order of precedence): 159 | 160 | * When a method expects a function, the named argument is `fn`. 161 | * When a method expects a number, the named argument is `n`. 162 | * When a method expects a value, the named argument is `v`. 163 | * When a method expects an array of functions, the named argument is `fns`. 164 | * When a method expects a single array, the named argument is `arr`. 165 | * When a method expects two arrays, the named arguments are `arr1` and `arr2`. 166 | * When a method expects a single object-property name, the named argument is `prop`. 167 | * When a method expects a list of object-property names, the named argument is `props`. 168 | * When a mapper function is called, it will be passed these named arguments: `v` (value), `i` (index), `arr` (array). 169 | * When a predicate function is called, it will be passed these named arguments: `v` (value), `i` (index), `arr` (array). 170 | * When a reducer function is called, it will be passed these named arguments: `acc` (accumulator), `v` (value), `i` (index), `arr` (array). 171 | * When a transducer combination function is called, it will be passed these named arguments: `acc` (accumulator), `v` (value). 172 | 173 | Some exceptions to these naming conventions: 174 | 175 | * [`FPO.setProp(..)`](docs/core-API.md#fposetprop) expects: `prop` (object-property name), `o` (object), `v` (value). 176 | * [`FPO.partial(..)`](docs/core-API.md#fpopartial) expects: `fn` (function), `args` (object containing the named-argument/value pairs to partially apply). 177 | * [`FPO.flatten(..)`](docs/core-API.md#fpoflatten) expects: `v` (array), `n` (count of nesting levels to flatten out). 178 | * [`FPO.transduce(..)`](docs/core-API.md#fpotransduce) expects: `fn` (transducer function), `co` (combination function), `v` (initial value), `arr` (array). 179 | * [`FPO.compose(..)`](docs/core-API.md#fpocompose) and [`FPO.pipe(..)`](core.API.md#fpopipe) produce functions that expect a `{ v: .. }` object argument. These utilities further assume that each function in the composition expects the output of the previous function to be rewrapped in a `{ v: .. }`-style object argument. 180 | 181 | This also applies to transducers. [`FPO.transducers.filter(..)`](docs/core-API.md#fpotransducersfilter) and [`FPO.transducers.map(..)`](docs/core-API.md#fpotransducersmap), whether composed together or used standalone, are curried to expect the combination function to be passed to them as a `{ v: .. }`-style object argument. 182 | * [`FPO.reassoc(..)`](docs/core-API.md#fporeassoc) expects: `props` (object with `sourceProp: targetProp` remapping key/value pairs), `v` (object) 183 | 184 | ## Arity 185 | 186 | Arity is typically defined as the number of declared parameters (expected arguments) for a function. With traditional style functions, this is just a simple numeric count. 187 | 188 | For named-argument style functions, the situation is more nuanced. One interpretation of arity would be a raw count of named-arguments (how many properties present). Another interpretation would limit this counting to only the set of expected named-arguments. 189 | 190 | For currying, FPO assumes the straight numeric count interpretation. [`FPO.curry( {fn: foo, n: 3} )`](docs/core-API.md#fpocurry) makes a curried function that accepts the first 3 properties, one at a time, regardless of what they're named. 191 | 192 | For [`unary(..)`](docs/core-API.md#fpounary), [`binary(..)`](docs/core-API.md#fpobinary), and [`nAry(..)`](docs/core-API.md#fponary), FPO requires a list of properties (`props`) to filter through for the underlying function. `FPO.binary({fn: foo, props:["x","y"]})` makes a function that only lets `x` and `y` named arguments through to `foo(..)`. 193 | 194 | ## Currying 195 | 196 | The strictest definition of currying is that each call only allows through one argument (`foo(1)(2)(3)`). That's consistent with how currying works in Haskell, for example. 197 | 198 | However, for convenience, most FP libraries in JS use a looser definition of currying where multiple arguments can be passed in with each call (`foo(1,2)(3)`). 199 | 200 | FPO supports both approaches. [`FPO.curry(..)`](docs/core-API.md#fpocurry) (and [`FPO.std.curry(..)`](docs/std-API.md#fpostdcurry)) use the stricter one-at-a-time definition -- subsequent arguments in each call are ignored -- while [`FPO.curryMultiple(..)`](docs/core-API.md#fpocurrymultiple) (and [`FPO.std.curryMultiple(..)`](docs/std-API.md#fpostdcurrymultiple)) use the looser multiple-arguments definition. 201 | 202 | All FPO methods are multiple-curried for convenience. 203 | 204 | ## Builds 205 | 206 | [![Build Status](https://travis-ci.org/getify/fpo.svg?branch=master)](https://travis-ci.org/getify/fpo) 207 | [![npm Module](https://badge.fury.io/js/fpo.svg)](https://www.npmjs.org/package/fpo) 208 | 209 | The distribution library file (`dist/fpo.js`) comes pre-built with the npm package distribution, so you shouldn't need to rebuild it under normal circumstances. 210 | 211 | However, if you download this repository via Git: 212 | 213 | 1. The included build utility (`scripts/build-core.js`) builds (and ~~minifies~~) `dist/fpo.js` from source. **Note:** Minification is currently disabled. **The build utility expects Node.js version 6+.** 214 | 215 | 2. To install the build and test dependencies, run `npm install` from the project root directory. 216 | 217 | 3. Because of how npm lifecyle events (currently: npm v4) work, `npm install` will have the side effect of automatically running the build and test utilities for you. So, no further action should be needed on your part. Starting with npm v5, the build utility will still be run automatically on `npm install`, but the test utility will not. 218 | 219 | To run the build utility with npm: 220 | 221 | ``` 222 | npm run build 223 | ``` 224 | 225 | To run the build utility directly without npm: 226 | 227 | ``` 228 | node scripts/build-core.js 229 | ``` 230 | 231 | ## Tests 232 | 233 | A comprehensive test suite is included in this repository, as well as the npm package distribution. The default test behavior runs the test suite using `src/fpo.src.js`. 234 | 235 | 1. You can run the tests in a browser by opening up `tests/index.html` (**requires ES6+ browser environment**). 236 | 237 | 2. The included Node.js test utility (`scripts/node-tests.js`) runs the test suite. **This test utility expects Node.js version 6+.** 238 | 239 | 3. Ensure the Node.js test utility dependencies are installed by running `npm install` from the project root directory. 240 | 241 | 4. Because of how npm lifecyle events (currently: npm v4) work, `npm install` will have the side effect of automatically running the build and test utilities for you. So, no further action should be needed on your part. Starting with npm v5, the build utility will still be run automatically on `npm install`, but the test utility will not. 242 | 243 | To run the test utility with npm: 244 | 245 | ``` 246 | npm test 247 | ``` 248 | 249 | Other npm test scripts: 250 | 251 | * `npm run test:dist` will run the test suite against `dist/fpo.js`. 252 | 253 | * `npm run test:package` will run the test suite as if the package had just been installed via npm. This ensures `package.json`:`main` properly references `dist/fpo.js` for inclusion. 254 | 255 | * `npm run test:all` will run all three modes of the test suite. This is what's automatically run when you first `npm install` the build and test dependencies. 256 | 257 | To run the test utility directly without npm: 258 | 259 | ``` 260 | node scripts/node-tests.js 261 | ``` 262 | 263 | ### Test Coverage 264 | 265 | [![Coverage Status](https://coveralls.io/repos/github/getify/fpo/badge.svg?branch=master)](https://coveralls.io/github/getify/fpo?branch=master) 266 | 267 | If you have [Istanbul](https://github.com/gotwarlost/istanbul) already installed on your system (requires v1.0+), you can use it to check the test coverage: 268 | 269 | ``` 270 | npm run coverage 271 | ``` 272 | 273 | Then open up `coverage/lcov-report/index.html` in a browser to view the report. 274 | 275 | To run Istanbul directly without npm: 276 | 277 | ``` 278 | istanbul cover scripts/node-tests.js 279 | ``` 280 | 281 | **Note:** The npm script `coverage:report` is only intended for use by project maintainers. It sends coverage reports to [Coveralls](https://coveralls.io/). 282 | 283 | ## License 284 | 285 | All code and documentation are (c) 2017 Kyle Simpson and released under the [MIT License](http://getify.mit-license.org/). A copy of the MIT License [is also included](LICENSE.txt). 286 | -------------------------------------------------------------------------------- /src/fpo.src.js: -------------------------------------------------------------------------------- 1 | (function UMD(name,context,definition){ 2 | /* istanbul ignore next */if (typeof define === "function" && define.amd) { define(definition); } 3 | /* istanbul ignore next */else if (typeof module !== "undefined" && module.exports) { module.exports = definition(); } 4 | /* istanbul ignore next */else { context[name] = definition(name,context); } 5 | })("FPO",this,function DEF(name,context){ 6 | "use strict"; 7 | 8 | var publicAPI = { 9 | identity: curryMultiple( {fn: identity, n: 1} ), 10 | constant: curryMultiple( {fn: constant, n: 1} ), 11 | pick: curryMultiple( {fn: pick, n: 2} ), 12 | pickAll: curryMultiple( {fn: pickAll, n: 2} ), 13 | nAry: curryMultiple( {fn: nAry, n: 2} ), 14 | unary: curryMultiple( {fn: unary, n: 2} ), 15 | binary: curryMultiple( {fn: binary, n: 2} ), 16 | curry: curryMultiple( {fn: curry, n: 1} ), 17 | curryMultiple: curryMultiple( {fn: curryMultiple, n: 1} ), 18 | uncurry: curryMultiple( {fn: uncurry, n: 1} ), 19 | partial: curryMultiple( {fn: partial, n: 2 } ), 20 | complement: curryMultiple( {fn: complement, n: 1} ), 21 | apply: curryMultiple( {fn: apply, n: 1} ), 22 | unapply: curryMultiple( {fn: unapply, n: 2} ), 23 | compose: curryMultiple( {fn: compose, n: 1} ), 24 | pipe: curryMultiple( {fn: pipe, n: 1} ), 25 | prop: curryMultiple( {fn: prop, n: 2} ), 26 | setProp: curryMultiple( {fn: setProp, n: 3} ), 27 | reassoc: curryMultiple( {fn: reassoc, n: 2} ), 28 | filterIn: curryMultiple( {fn: filterIn, n: 2} ), 29 | filterInObj: curryMultiple( {fn: filterInObj, n: 2} ), 30 | filterOut: curryMultiple( {fn: filterOut, n: 2} ), 31 | filterOutObj: curryMultiple( {fn: filterOutObj, n: 2} ), 32 | map: curryMultiple( {fn: map, n: 2} ), 33 | mapObj: curryMultiple( {fn: mapObj, n: 2} ), 34 | flatMap: curryMultiple( {fn: flatMap, n: 2} ), 35 | flatMapObj: curryMultiple( {fn: flatMapObj, n: 2} ), 36 | reduce: curryMultiple( {fn: reduce, n: 2} ), 37 | reduceObj: curryMultiple( {fn: reduceObj, n: 2} ), 38 | reduceRight: curryMultiple( {fn: reduceRight, n: 2} ), 39 | flatten: curryMultiple( {fn: flatten, n:1} ), 40 | zip: curryMultiple( {fn: zip, n: 2} ), 41 | trampoline: curryMultiple( {fn: trampoline, n: 1} ), 42 | transducers: { 43 | transduce: curryMultiple( {fn: transduce, n: 4} ), 44 | into: curryMultiple( {fn: into, n: 3} ), 45 | map: curryMultiple( {fn: transducerMap, n: 1} ), 46 | filter: curryMultiple( {fn: transducerFilter, n: 1} ), 47 | string: curryMultiple( {fn: strConcat, n: 2} ), 48 | array: curryMultiple( {fn: arrayPush, n: 2} ), 49 | number: curryMultiple( {fn: numericAdd, n: 2} ), 50 | booleanAnd: curryMultiple( {fn: booleanAnd, n: 2} ), 51 | booleanOr: curryMultiple( {fn: booleanOr, n: 2} ), 52 | default: curryMultiple( {fn: ({ acc }) => acc, n: 1} ), 53 | }, 54 | head: curryMultiple( {fn: head, n: 1} ), 55 | tail: curryMultiple( {fn: tail, n: 1} ), 56 | take: curryMultiple( {fn: take, n: 1} ), 57 | memoize: curryMultiple( {fn: memoize, n: 1} ), 58 | remap: curryMultiple( {fn: remap, n: 2} ), 59 | }; 60 | 61 | publicAPI.std = { 62 | identity: stdCurryMultiple( unapply( {fn: identity, props: ["v"]} ), /*arity=*/1 ), 63 | constant: stdCurryMultiple( unapply( {fn: constant, props: ["v"]} ), /*arity=*/1 ), 64 | pick: stdCurryMultiple( unapply( {fn: pick, props: ["props","v"]} ), /*arity=*/2 ), 65 | pickAll: stdCurryMultiple( unapply( {fn: pickAll, props: ["props","v"]} ), /*arity=*/2 ), 66 | nAry: stdCurryMultiple( stdNAry, /*arity=*/2 ), 67 | unary: stdCurryMultiple( stdUnary, /*arity=*/1 ), 68 | binary: stdCurryMultiple( stdBinary, /*arity=*/1 ), 69 | curry: stdCurryMultiple( stdCurry, /*arity=*/1 ), 70 | curryMultiple: stdCurryMultiple( stdCurryMultiple, /*arity=*/1 ), 71 | uncurry: stdCurryMultiple( stdUncurry, /*arity=*/1 ), 72 | partial: stdCurryMultiple( stdPartial, /*arity=*/2 ), 73 | partialRight: stdCurryMultiple( stdPartialRight, /*arity=*/2 ), 74 | complement: stdCurryMultiple( unapply( {fn: complement, props: ["fn"]} ), /*arity=*/1 ), 75 | apply: stdCurryMultiple( stdApply, /*arity=*/1 ), 76 | unapply: stdCurryMultiple( stdUnapply, /*arity=*/1 ), 77 | compose: stdCurryMultiple( stdCompose, /*arity=*/1 ), 78 | pipe: stdCurryMultiple( stdPipe, /*arity=*/1 ), 79 | prop: stdCurryMultiple( unapply( {fn: prop, props: ["prop","v"]} ), /*arity=*/2 ), 80 | setProp: stdCurryMultiple( unapply( {fn: setProp, props: ["prop","o","v"]} ), /*arity=*/3 ), 81 | reassoc: stdCurryMultiple( unapply( {fn: reassoc, props: ["props","v"]} ), /*arity=*/2 ), 82 | filterIn: stdCurryMultiple( unapply( {fn: _applyFnProp( filterIn, ["v","i","arr"] ), props: ["fn","arr"]} ), /*arity=*/2 ), 83 | filterInObj: stdCurryMultiple( unapply( {fn: _applyFnProp( filterInObj, ["v","i","o"] ), props: ["fn","o"]} ), /*arity=*/2 ), 84 | filterOut: stdCurryMultiple( unapply( {fn: _applyFnProp( filterOut, ["v","i","arr"] ), props: ["fn","arr"]} ), /*arity=*/2 ), 85 | filterOutObj: stdCurryMultiple( unapply( {fn: _applyFnProp( filterOutObj, ["v","i","o"] ), props: ["fn","o"]} ), /*arity=*/2 ), 86 | map: stdCurryMultiple( unapply( {fn: _applyFnProp( map, ["v","i","arr"] ), props: ["fn","arr"]} ), /*arity=*/2 ), 87 | mapObj: stdCurryMultiple( unapply( {fn: _applyFnProp( mapObj, ["v","i","o"] ), props: ["fn","o"]} ), /*arity=*/2 ), 88 | flatMap: stdCurryMultiple( unapply( {fn: _applyFnProp( flatMap, ["v","i","arr"] ), props: ["fn","arr"]}), /*arity=*/2 ), 89 | flatMapObj: stdCurryMultiple( unapply( {fn: _applyFnProp( flatMapObj, ["v","i","o"] ), props: ["fn","o"]}), /*arity=*/2 ), 90 | reduce: stdCurryMultiple( unapply( {fn: _applyFnProp( reduce, ["acc","v","i","arr"] ), props: ["fn","v","arr"]} ), /*arity=*/3 ), 91 | reduceObj: stdCurryMultiple( unapply( {fn: _applyFnProp( reduceObj, ["acc","v","i","o"] ), props: ["fn","v","o"]} ), /*arity=*/3 ), 92 | reduceRight: stdCurryMultiple( unapply( {fn: _applyFnProp( reduceRight, ["acc","v","i","arr"] ), props: ["fn","v","arr"]} ), /*arity=*/3 ), 93 | flatten: stdCurryMultiple( unapply( {fn: flatten, props: ["v","n"]} ), /*arity=*/1 ), 94 | zip: stdCurryMultiple( unapply( {fn: zip, props: ["arr1","arr2"]} ), /*arity=*/2 ), 95 | trampoline: stdCurryMultiple( unapply( {fn: trampoline, props: ["fn"]} ), /*arity=*/1 ), 96 | transducers: { 97 | transduce: stdCurryMultiple( stdTransduce, /*arity=*/4 ), 98 | into: stdCurryMultiple( stdInto, /*arity=*/3 ), 99 | map: stdCurryMultiple( stdTransducerMap, /*arity=*/2 ), 100 | filter: stdCurryMultiple( stdTransducerFilter, /*arity=*/2 ), 101 | string: stdCurryMultiple( stdStrConcat, /*arity=*/2 ), 102 | array: stdCurryMultiple( stdArrayPush, /*arity=*/2 ), 103 | number: stdCurryMultiple( stdNumericAdd, /*arity=*/2 ), 104 | booleanAnd: stdCurryMultiple( stdBooleanAnd, /*arity=*/2 ), 105 | booleanOr: stdCurryMultiple( stdBooleanOr, /*arity=*/2 ), 106 | default: stdCurryMultiple( acc => acc, /*arity=*/1 ), 107 | }, 108 | flip: stdCurryMultiple( stdFlip, /*arity=*/1 ), 109 | reverseArgs: stdCurryMultiple( stdReverseArgs, /*arity=*/1 ), 110 | head: stdCurryMultiple( unapply( {fn: head, props: ["v"]} ), /*arity=*/1 ), 111 | tail: stdCurryMultiple( unapply( {fn: tail, props: ["v"]} ), /*arity=*/1 ), 112 | take: stdCurryMultiple( unapply( {fn: take, props: ["v","n"]} ), /*arity=*/1 ), 113 | memoize: stdCurryMultiple( unapply( {fn: memoize, props: ["fn","n"]} ), /*arity=*/1 ), 114 | remap: stdCurryMultiple( unapply( {fn: remap, props: ["fn","args"]} ), /*arity=*/2 ), 115 | }; 116 | 117 | // method convenience aliases 118 | _setMethodAlias("constant","always"); 119 | _setMethodAlias("pipe","flow"); 120 | _setMethodAlias("pipe","sequence"); 121 | _setMethodAlias("compose","flowRight"); 122 | _setMethodAlias("apply","spread"); 123 | _setMethodAlias("unapply","gather"); 124 | _setMethodAlias("setProp","assoc"); 125 | _setMethodAlias("filterIn","filter"); 126 | _setMethodAlias("filterIn","keep"); 127 | _setMethodAlias("filterInObj","filterObj"); 128 | _setMethodAlias("filterInObj","keepObj"); 129 | _setMethodAlias("filterOut","reject"); 130 | _setMethodAlias("filterOutObj","rejectObj"); 131 | _setMethodAlias("flatMap","chain"); 132 | _setMethodAlias("flatMapObj","chainObj"); 133 | _setMethodAlias("reduce","fold"); 134 | _setMethodAlias("reduceObj","foldObj"); 135 | _setMethodAlias("reduce","foldL"); 136 | _setMethodAlias("reduceRight","foldR"); 137 | publicAPI.partialRight = publicAPI.partial; 138 | publicAPI.transducers.boolean = publicAPI.transducers.booleanAnd; 139 | publicAPI.std.transducers.boolean = publicAPI.std.transducers.booleanAnd; 140 | 141 | return publicAPI; 142 | 143 | 144 | // *************************************** 145 | 146 | function identity({ v }) { 147 | return v; 148 | } 149 | 150 | function constant({ v }) { 151 | return function value(){ 152 | return v; 153 | }; 154 | } 155 | 156 | function pick({ v: obj, props = [] }) { 157 | var newObj = {}; 158 | 159 | for (let prop of props) { 160 | if (_hasProp( obj, prop )) { 161 | newObj[prop] = obj[prop]; 162 | } 163 | } 164 | 165 | return newObj; 166 | } 167 | 168 | function pickAll({ v: obj, props = [] }) { 169 | var newObj = {}; 170 | 171 | for (let prop of props) { 172 | newObj[prop] = obj[prop]; 173 | } 174 | 175 | return newObj; 176 | } 177 | 178 | function nAry({ fn, props = [] }) { 179 | return function limited(argsObj = {}){ 180 | return fn( pick( {v: argsObj, props} ) ); 181 | }; 182 | } 183 | 184 | function stdNAry(fn,n = 0) { 185 | n = Number( n ); 186 | 187 | return function nary(...args){ 188 | return fn( ...args.slice( 0, Math.max( 0, n ) ) ); 189 | }; 190 | } 191 | 192 | function unary({ fn, prop: propName1 = "" }) { 193 | return nAry( {fn, props: [propName1]} ); 194 | } 195 | 196 | function stdUnary(fn) { 197 | return stdNAry( fn, 1 ); 198 | } 199 | 200 | function binary({ fn, props: [ propName1 = "", propName2 = "" ] = [] }) { 201 | return nAry( {fn, props: [propName1,propName2]} ); 202 | } 203 | 204 | function stdBinary(fn) { 205 | return stdNAry( fn, 2 ); 206 | } 207 | 208 | function curry({ fn, n: arity = 1 }) { 209 | arity = Number( arity ); 210 | 211 | return (function nextCurried(prevArgsObj){ 212 | return function curried(nextArgsObj = {}){ 213 | var keys = Object.keys( nextArgsObj ); 214 | var allArgsObj = (keys.length > 0) ? 215 | Object.assign( {}, prevArgsObj, {[keys[0]]: nextArgsObj[keys[0]]} ) : 216 | prevArgsObj; 217 | 218 | if (Object.keys( allArgsObj ).length >= arity) { 219 | return fn( allArgsObj ); 220 | } 221 | else { 222 | return nextCurried( allArgsObj ); 223 | } 224 | }; 225 | })( {} ); 226 | } 227 | 228 | function stdCurry(fn,arity = Math.max( 1, fn.length)) { 229 | arity = Number( arity ); 230 | 231 | return (function nextCurried(prevArgs){ 232 | return function curried(...nextArgs){ 233 | var allArgs = (nextArgs.length > 0) ? 234 | prevArgs.concat( [nextArgs[0]] ) : 235 | prevArgs; 236 | 237 | if (allArgs.length >= arity) { 238 | return fn( ...allArgs ); 239 | } 240 | else { 241 | return nextCurried( allArgs ); 242 | } 243 | }; 244 | })( [] ); 245 | } 246 | 247 | function curryMultiple({ fn, n: arity = 1 }) { 248 | arity = Number( arity ); 249 | 250 | return (function nextCurried(prevArgsObj){ 251 | return function curried(nextArgsObj = {}){ 252 | var allArgsObj = (Object.keys( nextArgsObj ).length > 0) ? 253 | Object.assign( {}, prevArgsObj, nextArgsObj ) : 254 | prevArgsObj; 255 | 256 | if (Object.keys( allArgsObj ).length >= arity) { 257 | return fn( allArgsObj ); 258 | } 259 | else { 260 | return nextCurried( allArgsObj ); 261 | } 262 | }; 263 | })( {} ); 264 | } 265 | 266 | function stdCurryMultiple(fn,arity = Math.max( 1, fn.length)) { 267 | arity = Number( arity ); 268 | 269 | return (function nextCurried(prevArgs){ 270 | return function curried(...nextArgs){ 271 | var allArgs = (nextArgs.length > 0) ? 272 | prevArgs.concat( nextArgs ) : 273 | prevArgs; 274 | 275 | if (allArgs.length >= arity) { 276 | return fn( ...allArgs ); 277 | } 278 | else { 279 | return nextCurried( allArgs ); 280 | } 281 | }; 282 | })( [] ); 283 | } 284 | 285 | function uncurry({ fn }) { 286 | return function uncurried(argsObj = {}){ 287 | var ret = fn; 288 | 289 | for (let prop of Object.keys( argsObj )) { 290 | // assume `fn` is strictly curried (needs props one at a time), 291 | // instead of loose/multiple currying 292 | ret = ret( {[prop]: argsObj[prop]} ); 293 | } 294 | 295 | return ret; 296 | }; 297 | } 298 | 299 | function stdUncurry(fn) { 300 | return function uncurried(...args){ 301 | var ret = fn; 302 | 303 | for (let v of args) { 304 | // assume `fn` is strictly curried (needs one arg at a time), 305 | // instead of loose/multiple currying 306 | ret = ret( v ); 307 | } 308 | 309 | return ret; 310 | }; 311 | } 312 | 313 | function partial({ fn, args: partialArgsObj = {} }) { 314 | return function partiallyApplied(restArgsObj = {}){ 315 | return fn( Object.assign( {}, partialArgsObj, restArgsObj ) ); 316 | }; 317 | } 318 | 319 | function stdPartial(fn,partialArgs = []) { 320 | return function partiallyApplied(...restArgs){ 321 | return fn( ...partialArgs, ...restArgs ); 322 | }; 323 | } 324 | 325 | function stdPartialRight(fn,partialArgs = []) { 326 | return function partiallyApplied(...restArgs){ 327 | return fn( ...restArgs, ...partialArgs ); 328 | }; 329 | } 330 | 331 | function complement({ fn: predicateFn }) { 332 | return function complemented(...args){ 333 | return !predicateFn( ...args ); 334 | }; 335 | } 336 | 337 | function apply({ 338 | fn, 339 | props: propNamesInOrder = fn.toString() 340 | .replace( /^(?:(?:function.*\(([^]*?)\))|(?:([^\(\)]+?)\s*=>)|(?:\(([^]*?)\)\s*=>))[^]+$/, "$1$2$3" ) 341 | .split( /\s*,\s*/ ) 342 | .map( v => v.replace( /[=\s].*$/, "" ) ) 343 | }) { 344 | return function appliedFn(argsObj) { 345 | return fn( ...propNamesInOrder.map( function getPropVal(k) { return argsObj[k]; } ) ); 346 | }; 347 | } 348 | 349 | function stdApply(fn) { 350 | return function appliedFn(argsArr) { 351 | return fn( ...argsArr ); 352 | }; 353 | } 354 | 355 | function unapply({ fn, props: propNamesInOrder = [] }) { 356 | return function unappliedFn(...args) { 357 | var argsObj = {}; 358 | var i1 = 0; 359 | var i2 = 0; 360 | 361 | while (i1 < propNamesInOrder.length && i2 < args.length) { 362 | argsObj[propNamesInOrder[i1++]] = args[i2++]; 363 | } 364 | 365 | return fn( argsObj ); 366 | }; 367 | } 368 | 369 | function stdUnapply(fn) { 370 | return function unappliedFn(...argsArr) { 371 | return fn( argsArr ); 372 | }; 373 | } 374 | 375 | function compose({ fns = [] }) { 376 | return function composed( {v: result} ){ 377 | for (let i = fns.length - 1; i >= 0; i--) { 378 | result = fns[i]( {v: result} ); 379 | } 380 | 381 | return result; 382 | }; 383 | } 384 | 385 | function stdCompose(fns = []) { 386 | return function composed(result){ 387 | for (let i = fns.length - 1; i >= 0; i--) { 388 | result = fns[i]( result ); 389 | } 390 | 391 | return result; 392 | }; 393 | } 394 | 395 | function pipe({ fns = [] }) { 396 | return function piped( {v: result} ){ 397 | for (let fn of fns) { 398 | result = fn( {v: result} ); 399 | } 400 | 401 | return result; 402 | }; 403 | } 404 | 405 | function stdPipe(fns = []) { 406 | return function piped(result){ 407 | for (let fn of fns) { 408 | result = fn( result ); 409 | } 410 | 411 | return result; 412 | }; 413 | } 414 | 415 | function prop({ prop = "", v: obj = {} }) { 416 | return obj[prop]; 417 | } 418 | 419 | function setProp({ prop = "", o: obj = {}, v }) { 420 | obj = Object.assign( {}, obj ); 421 | obj[prop] = v; 422 | return obj; 423 | } 424 | 425 | function reassoc({ props = {}, v }) { 426 | var obj = {}; 427 | var sourceProps = Object.keys( props ); 428 | 429 | // first, remap specified properties 430 | for (let sourceProp of sourceProps) { 431 | if (sourceProp in v) { 432 | obj[props[sourceProp]] = v[sourceProp]; 433 | } 434 | } 435 | 436 | // then, copy (only) other properties 437 | for (let prop of Object.keys( v )) { 438 | if (!~sourceProps.indexOf( prop )) { 439 | obj[prop] = v[prop]; 440 | } 441 | } 442 | 443 | return obj; 444 | } 445 | 446 | function filterIn({ fn: predicateFn, arr = [] }) { 447 | var newArr = []; 448 | 449 | for (let [i,v] of arr.entries()) { 450 | if (predicateFn( {v, i, arr} )) { 451 | newArr[newArr.length] = v; 452 | } 453 | } 454 | 455 | return newArr; 456 | } 457 | 458 | function filterInObj({ fn: predicateFn, o = {} }) { 459 | var newObj = {}; 460 | 461 | for (let i of Object.keys( o )) { 462 | if (predicateFn( {v: o[i], i, o} )) { 463 | newObj[i] = o[i]; 464 | } 465 | } 466 | 467 | return newObj; 468 | } 469 | 470 | function filterOut({ fn: predicateFn, arr = [] }) { 471 | return filterIn( {fn: complement( {fn: predicateFn} ), arr} ); 472 | } 473 | 474 | function filterOutObj({ fn: predicateFn, o = {} }) { 475 | return filterInObj( {fn: complement( {fn: predicateFn} ), o} ); 476 | } 477 | 478 | function map({ fn: mapperFn, arr = [] }) { 479 | var newArr = []; 480 | 481 | for (let [i,v] of arr.entries()) { 482 | newArr[i] = mapperFn( {v, i, arr} ); 483 | } 484 | 485 | return newArr; 486 | } 487 | 488 | function mapObj({ fn: mapperFn, o = {} }) { 489 | var newObj = {}; 490 | 491 | for (let i of Object.keys( o )) { 492 | newObj[i] = mapperFn( {v: o[i], i, o} ); 493 | } 494 | 495 | return newObj; 496 | } 497 | 498 | function flatMap({ fn: mapperFn, arr = [] }) { 499 | var newArr = []; 500 | 501 | for (let [i,v] of arr.entries()) { 502 | newArr = newArr.concat( mapperFn( {v, i, arr} ) ); 503 | } 504 | 505 | return newArr; 506 | } 507 | 508 | function flatMapObj({ fn: mapperFn, o = {} }) { 509 | var newObj = {}; 510 | 511 | for (let i of Object.keys(o)) { 512 | let ret = mapperFn( {v: o[i], i, o} ); 513 | if (typeof ret == "object" && !Array.isArray( ret )) { 514 | Object.assign( newObj, ret ); 515 | } 516 | else { 517 | newObj[i] = ret; 518 | } 519 | } 520 | 521 | return newObj; 522 | } 523 | 524 | function reduce({ fn: reducerFn, v: initialValue, arr = [] }) { 525 | var origArr = arr; 526 | var i = 0; 527 | 528 | if (initialValue === undefined && arr.length > 0) { 529 | initialValue = arr[0]; 530 | arr = arr.slice( 1 ); 531 | i++; 532 | } 533 | 534 | for (let v of arr) { 535 | initialValue = reducerFn( {acc: initialValue, v, i: i++, arr: origArr} ); 536 | } 537 | 538 | return initialValue; 539 | } 540 | 541 | function reduceObj({ fn: reducerFn, v: initialValue, o = {} }) { 542 | var keys = Object.keys( o ); 543 | 544 | if (initialValue === undefined && keys.length > 0) { 545 | initialValue = o[keys[0]]; 546 | keys = keys.slice( 1 ); 547 | } 548 | 549 | for (let i of keys) { 550 | initialValue = reducerFn( {acc: initialValue, v: o[i], i, o} ); 551 | } 552 | 553 | return initialValue; 554 | } 555 | 556 | function reduceRight({ fn: reducerFn, v: initialValue, arr = [] }) { 557 | var origArr = arr; 558 | var idx = arr.length - 1; 559 | 560 | if (initialValue === undefined && arr.length > 0) { 561 | initialValue = arr[idx]; 562 | arr = arr.slice( 0, idx ); 563 | idx--; 564 | } 565 | 566 | for (let i = arr.length - 1; i >= 0; i--) { 567 | initialValue = reducerFn( {acc: initialValue, v: arr[i], i: idx--, arr: origArr} ); 568 | } 569 | 570 | return initialValue; 571 | } 572 | 573 | function flatten({ v: arr = [], n: depth = Infinity }) { 574 | depth = Number( depth ); 575 | var list = []; 576 | 577 | for (let v of arr) { 578 | list = list.concat( 579 | depth > 0 ? 580 | (depth > 1 && Array.isArray( v ) ? 581 | flatten( {v, n: depth - 1} ) : 582 | v 583 | ) : 584 | [v] 585 | ); 586 | } 587 | 588 | return list; 589 | } 590 | 591 | function zip({ arr1 = [], arr2 = [] }) { 592 | var zipped = []; 593 | var i1 = 0; 594 | var i2 = 0; 595 | 596 | while (i1 < arr1.length && i2 < arr2.length) { 597 | zipped.push( [arr1[i1++], arr2[i2++]] ); 598 | } 599 | 600 | return zipped; 601 | } 602 | 603 | function trampoline({ fn }) { 604 | return function trampolined(...args) { 605 | var result = fn( ...args ); 606 | 607 | while (typeof result == "function") { 608 | result = result(); 609 | } 610 | 611 | return result; 612 | }; 613 | } 614 | 615 | function transduce({ fn: transducer, co: combinationFn, v: initialValue, arr = [] }) { 616 | var reducer = transducer( {v: combinationFn} ); 617 | 618 | return reduce( {fn: reducer, v: initialValue, arr} ); 619 | } 620 | 621 | function stdTransduce(transducer,combinationFn,initialValue,arr = []) { 622 | var reducer = transducer( combinationFn ); 623 | 624 | return publicAPI.std.reduce( reducer, initialValue, arr ); 625 | } 626 | 627 | function into({ fn: transducer, v: initialValue, arr = [] }) { 628 | var combinationFn = 629 | typeof initialValue == "string" ? strConcat : 630 | typeof initialValue == "number" ? numericAdd : 631 | typeof initialValue == "boolean" ? booleanAnd : 632 | Array.isArray( initialValue ) ? arrayPush : 633 | publicAPI.transducers.default; 634 | 635 | return transduce( {fn: transducer, co: combinationFn, v: initialValue, arr} ); 636 | } 637 | 638 | function stdInto(transducer,initialValue,arr = []) { 639 | var combinationFn = 640 | typeof initialValue == "string" ? stdStrConcat : 641 | typeof initialValue == "number" ? stdNumericAdd : 642 | typeof initialValue == "boolean" ? stdBooleanAnd : 643 | Array.isArray( initialValue ) ? stdArrayPush : 644 | publicAPI.std.transducers.default; 645 | 646 | return stdTransduce( transducer, combinationFn, initialValue, arr ); 647 | } 648 | 649 | function transducerMap(argsObj) { 650 | var { fn: mapperFn, v: combinationFn } = argsObj; 651 | 652 | // still waiting on the combination function? 653 | if (!_hasProp( argsObj, "v" ) || !combinationFn) { 654 | // Note: the combination function is usually a composed 655 | // function, so we expect the argument by itself, 656 | // not wrapped in an object 657 | return function curried({ v }){ 658 | return transducerMap( {fn: mapperFn, v} ); 659 | }; 660 | } 661 | 662 | return function reducer({ acc, v }){ 663 | return combinationFn( {acc, v: mapperFn( {v} )} ); 664 | }; 665 | } 666 | 667 | function stdTransducerMap(mapperFn,combinationFn) { 668 | return function reducer(acc,v){ 669 | return combinationFn( acc, mapperFn( v ) ); 670 | }; 671 | } 672 | 673 | function transducerFilter(argsObj) { 674 | var { fn: predicateFn, v: combinationFn } = argsObj; 675 | 676 | // still waiting on the combination function? 677 | if (!_hasProp( argsObj, "v" ) || !combinationFn) { 678 | // Note: the combination function is usually a composed 679 | // function, so we expect the argument by itself, 680 | // not wrapped in an object 681 | return function curried({ v }){ 682 | return transducerFilter( {fn: predicateFn, v} ); 683 | }; 684 | } 685 | 686 | return function reducer({ acc, v } = {}){ 687 | if (predicateFn( {v} )) { 688 | return combinationFn( {acc, v} ); 689 | } 690 | 691 | return acc; 692 | }; 693 | } 694 | 695 | function stdTransducerFilter(predicateFn,combinationFn) { 696 | return function reducer(acc,v){ 697 | if (predicateFn( v )) { 698 | return combinationFn( acc, v ); 699 | } 700 | 701 | return acc; 702 | }; 703 | } 704 | 705 | function strConcat({ acc, v }) { 706 | return String( acc ) + v; 707 | } 708 | 709 | function stdStrConcat(acc,v) { 710 | return String( acc ) + v; 711 | } 712 | 713 | function arrayPush({ acc, v }) { 714 | acc.push( v ); 715 | return acc; 716 | } 717 | 718 | function stdArrayPush(acc,v) { 719 | acc.push( v ); 720 | return acc; 721 | } 722 | 723 | function numericAdd({ acc, v }) { 724 | return (+acc) + (+v); 725 | } 726 | 727 | function stdNumericAdd(acc,v) { 728 | return (+acc) + (+v); 729 | } 730 | 731 | function booleanAnd({ acc, v }) { 732 | return !!acc && !!v; 733 | } 734 | 735 | function stdBooleanAnd(acc,v) { 736 | return !!acc && !!v; 737 | } 738 | 739 | function booleanOr({ acc, v }) { 740 | return !!acc || !!v; 741 | } 742 | 743 | function stdBooleanOr(acc,v) { 744 | return !!acc || !!v; 745 | } 746 | 747 | function stdFlip(fn) { 748 | return function flipped(arg1,arg2,...args){ 749 | return fn( arg2, arg1, ...args ); 750 | }; 751 | } 752 | 753 | function stdReverseArgs(fn) { 754 | return function reversed(...args){ 755 | return fn( ...args.reverse() ); 756 | }; 757 | } 758 | 759 | function head({ v = [] }) { 760 | if (v && (typeof v == "object" || typeof v == "string")) { 761 | return v[0]; 762 | } 763 | return v; 764 | } 765 | 766 | function tail({ v = [] }) { 767 | if (v && (typeof v == "object" || typeof v == "string")) { 768 | if (typeof v.slice == "function") { 769 | return v.slice( 1 ); 770 | } 771 | else { 772 | let props = Object.keys( v ).filter( k => k != "0" ); 773 | return pick( {v, props} ); 774 | } 775 | } 776 | return v; 777 | } 778 | 779 | function take({ v = [], n = 1}) { 780 | if ( 781 | v && 782 | (typeof v == "object" || typeof v == "string") && 783 | typeof v.slice == "function" 784 | ) { 785 | return v.slice( 0, n ); 786 | } 787 | return []; 788 | } 789 | 790 | // adapted from: https://github.com/caiogondim/fast-memoize.js 791 | function memoize({ fn, n = fn.length }) { 792 | var cache = {}; 793 | 794 | return Number( n ) > 1 ? memoizedMultipleArgs : memoizedSingleArg; 795 | 796 | 797 | // ********************* 798 | 799 | function memoizedSingleArg(arg,...otherArgs) { 800 | var hash = 801 | // arg is a primitive? 802 | ( 803 | arg == null || 804 | !(typeof arg == "object" || typeof arg == "function") 805 | ) ? 806 | arg : 807 | JSON.stringify( arg ); 808 | 809 | return (hash in cache) ? 810 | cache[hash] : 811 | (cache[hash] = fn( arg, ...otherArgs )); 812 | } 813 | 814 | function memoizedMultipleArgs(...args) { 815 | var arg = args[0]; 816 | var hash = 817 | // only one argument? 818 | args.length == 1 && 819 | // arg is a primitive? 820 | ( 821 | arg == null || 822 | !(typeof arg == "object" || typeof arg == "function") 823 | ) ? 824 | arg : 825 | JSON.stringify( args ); 826 | 827 | return (hash in cache) ? 828 | cache[hash] : 829 | (cache[hash] = fn( ...args )); 830 | } 831 | } 832 | 833 | function remap({ fn, args = {}}) { 834 | var props = {}; 835 | 836 | // transpose `args` from `target: source` to 837 | // `source: target` for `reassoc(..)` to use 838 | for (let prop of Object.keys( args )) { 839 | props[args[prop]] = prop; 840 | } 841 | 842 | return function remapped(argsObj){ 843 | return fn( reassoc( {v: argsObj, props} ) ); 844 | }; 845 | } 846 | 847 | 848 | // *************************************** 849 | // Private 850 | 851 | function _setMethodAlias(origName,aliasName) { 852 | publicAPI[aliasName] = publicAPI[origName]; 853 | publicAPI.std[aliasName] = publicAPI.std[origName]; 854 | } 855 | 856 | function _applyFnProp(fn,props) { 857 | return function fnApplied(argsObj){ 858 | argsObj = Object.assign( {}, argsObj ); 859 | argsObj.fn = apply( {fn: argsObj.fn, props} ); 860 | return fn( argsObj ); 861 | }; 862 | } 863 | 864 | function _hasProp(obj,prop) { 865 | return Object.hasOwnProperty.call( obj, prop ); 866 | } 867 | 868 | }); 869 | -------------------------------------------------------------------------------- /docs/core-API.md: -------------------------------------------------------------------------------- 1 | # Core API 2 | 3 | These are the methods on the `FPO.*` namespace. For the `FPO.std.*` methods, consult the [Standard API documentation](std-API.md). 4 | 5 | * [`FPO.apply(..)`](#fpoapply) (aliases: `FPO.spread(..)`) 6 | * [`FPO.binary(..)`](#fpobinary) 7 | * [`FPO.complement(..)`](#fpocomplement) 8 | * [`FPO.compose(..)`](#fpocompose) (aliases: `FPO.flowRight(..)`) 9 | * [`FPO.constant(..)`](#fpoconstant) 10 | * [`FPO.curry(..)`](#fpocurry) 11 | * [`FPO.curryMultiple(..)`](#fpocurrymultiple) 12 | * [`FPO.filterIn(..)`](#fpofilterin) (aliases: `FPO.filter(..)`, `FPO.keep(..)`) 13 | * [`FPO.filterInObj(..)`](#fpofilterinobj) (aliases: `FPO.filterObj(..)`, `FPO.keepObj(..)`) 14 | * [`FPO.filterOut(..)`](#fpofilterout) (aliases: `FPO.reject(..)`) 15 | * [`FPO.filterOutObj(..)`](#fpofilteroutobj) (aliases: `FPO.rejectObj(..)`) 16 | * [`FPO.flatMap(..)`](#fpoflatmap) (aliases: `FPO.chain(..)`) 17 | * [`FPO.flatMapObj(..)`](#fpoflatmapobj) (aliases: `FPO.chainObj(..)`) 18 | * [`FPO.flatten(..)`](#fpoflatten) 19 | * [`FPO.head(..)`](#fpohead) 20 | * [`FPO.identity(..)`](#fpoidentity) 21 | * [`FPO.map(..)`](#fpomap) 22 | * [`FPO.mapObj(..)`](#fpomapobj) 23 | * [`FPO.memoize(..)`](#fpomemoize) 24 | * [`FPO.nAry(..)`](#fponary) 25 | * [`FPO.partial(..)`](#fpopartial) (aliases: `FPO.partialRight(..)`) 26 | * [`FPO.pick(..)`](#fpopick) 27 | * [`FPO.pickAll(..)`](#fpopickall) 28 | * [`FPO.pipe(..)`](#fpopipe) (aliases: `FPO.flow(..)`, `FPO.sequence(..)`) 29 | * [`FPO.prop(..)`](#fpoprop) 30 | * [`FPO.reassoc(..)`](#fporeassoc) 31 | * [`FPO.reduce(..)`](#fporeduce) (aliases: `FPO.fold(..)`, `FPO.foldL(..)`) 32 | * [`FPO.reduceObj(..)`](#fporeduceobj) (aliases: `FPO.foldObj(..)`) 33 | * [`FPO.reduceRight(..)`](#fporeduceright) (aliases: `FPO.foldR(..)`) 34 | * [`FPO.remap(..)`](#fporemap) 35 | * [`FPO.setProp(..)`](#fposetprop) (aliases: `FPO.assoc(..)`) 36 | * [`FPO.tail(..)`](#fpotail) 37 | * [`FPO.take(..)`](#fpotake) 38 | * [`FPO.trampoline(..)`](#fpotrampoline) 39 | * **Transducers**: 40 | - [`FPO.transducers.array(..)`](#fpotransducersarray) 41 | - [`FPO.transducers.booleanAnd(..)`](#fpotransducersbooleanand) (aliases: `FPO.transducers.boolean(..)`) 42 | - [`FPO.transducers.booleanOr(..)`](#fpotransducersbooleanor) 43 | - [`FPO.transducers.default(..)`](#fpotransducersdefault) 44 | - [`FPO.transducers.filter(..)`](#fpotransducersfilter) 45 | - [`FPO.transducers.into(..)`](#fpotransducersinto) 46 | - [`FPO.transducers.map(..)`](#fpotransducersmap) 47 | - [`FPO.transducers.number(..)`](#fpotransducersnumber) 48 | - [`FPO.transducers.string(..)`](#fpotransducersstring) 49 | - [`FPO.transducers.transduce(..)`](#fpotransducerstransduce) 50 | * [`FPO.unapply(..)`](#fpounapply) (aliases: `FPO.gather(..)`) 51 | * [`FPO.unary(..)`](#fpounary) 52 | * [`FPO.uncurry(..)`](#fpouncurry) 53 | * [`FPO.zip(..)`](#fpozip) 54 | 55 | ---- 56 | 57 | ### `FPO.apply(..)` 58 | 59 | ([back to top](#core-api)) 60 | 61 | Wraps a function to spread out the properties from an object argument as individual positional arguments. 62 | 63 | * **Arguments:** 64 | - `fn`: function to wrap 65 | - `props`: (optional) list of property names (strings) to indicate the order to spread properties as individual arguments. If omitted, the signature of `fn` is parsed for its parameter list to try to determine an ordered property list; this detection only works for simple parameters (including those with default parameter value settings). 66 | 67 | * **Returns:** *function* 68 | 69 | * **Example:** 70 | 71 | ```js 72 | function foo(x,y = 2) { return x + y; } 73 | function bar([a,b],c) { return a + b + c; } 74 | 75 | var f = FPO.apply( {fn: foo} ); 76 | var p = FPO.apply( {fn: bar, props:["x","y"]} ); 77 | 78 | f( {x: 1, y: 1} ); // 2 79 | f( {x: 3} ); // 5 80 | p( {x: [1,2], y: 3} )); // 6 81 | ``` 82 | 83 | * **Aliases:** `FPO.spread(..)` 84 | 85 | * **See Also:** [`FPO.unapply(..)`](#fpounapply) 86 | 87 | ---- 88 | 89 | ### `FPO.binary(..)` 90 | 91 | ([back to top](#core-api)) 92 | 93 | Wraps a function to restrict its inputs to only two named arguments as specified. 94 | 95 | * **Arguments:** 96 | - `fn`: function to wrap 97 | - `props`: array of two property names to allow as named arguments 98 | 99 | * **Returns:** *function* 100 | 101 | * **Example:** 102 | 103 | ```js 104 | function foo(argsObj) { return argsObj; } 105 | 106 | var f = FPO.binary( {fn: foo, props: ["x","y"]} ); 107 | 108 | f( {x: 1, y: 2, z: 3} ); 109 | // { x:1, y:2 } 110 | ``` 111 | 112 | * **See Also:** [`FPO.nAry(..)`](#fponary), [`FPO.unary(..)`](#fpounary) 113 | 114 | ---- 115 | 116 | ### `FPO.complement(..)` 117 | 118 | ([back to top](#core-api)) 119 | 120 | Wraps a predicate function -- a function that produces `true` / `false` -- to negate its result. 121 | 122 | * **Arguments:** 123 | - `fn`: function to wrap 124 | 125 | * **Returns:** *function* 126 | 127 | * **Example:** 128 | 129 | ```js 130 | var f = FPO.complement( {fn: () => false} ); 131 | 132 | f(); // true 133 | ``` 134 | 135 | ---- 136 | 137 | ### `FPO.compose(..)` 138 | 139 | ([back to top](#core-api)) 140 | 141 | Produces a new function that's the composition of a list of functions. Functions are composed right-to-left (unlike [`FPO.pipe(..)`](#fpopipe)) from the array. 142 | 143 | * **Arguments:** 144 | - `fns`: array of functions 145 | 146 | * **Returns:** *function* 147 | 148 | * **Example:** 149 | 150 | ```js 151 | var f = FPO.compose( {fns: [ 152 | ({v}) => v + "3", 153 | ({v}) => v + "2", 154 | ({v}) => v + "1" 155 | ]} ); 156 | 157 | f( {v: "0"} ); // "0123" 158 | ``` 159 | 160 | * **Aliases:** `FPO.flowRight(..)` 161 | 162 | * **See Also:** [`FPO.pipe(..)`](#fpopipe) 163 | 164 | ---- 165 | 166 | ### `FPO.constant(..)` 167 | 168 | ([back to top](#core-api)) 169 | 170 | Wraps a value in a function that returns the value. 171 | 172 | * **Arguments:** 173 | - `v`: value to wrap 174 | 175 | * **Returns:** *function* 176 | 177 | * **Example:** 178 | 179 | ```js 180 | var f = FPO.constant( {v: 42} ); 181 | 182 | f(); // 42 183 | ``` 184 | 185 | * **See Also:** [`FPO.identity(..)`](#fpoidentity) 186 | 187 | ---- 188 | 189 | ### `FPO.curry(..)` 190 | 191 | ([back to top](#core-api)) 192 | 193 | Curries a function so that you can pass one argument at a time, each time getting back another function to receive the next argument. Once all arguments are passed, the underlying function is called with the arguments. 194 | 195 | Unlike [`FPO.curryMultiple(..)`](#fpocurrymultiple), you can only pass one property argument at a time to each curried function (see example below). If multiple properties are passed to a curried call, only the first property (in enumeration order) will be passed. 196 | 197 | **Note:** Enumeration order of properties is not strictly guaranteed cross-environment. However, it's generally reliable as the order that properties were listed/added to the object in its definition. 198 | 199 | * **Arguments:** 200 | - `fn`: function to curry 201 | - `n`: number of arguments to curry for 202 | 203 | * **Returns:** *function* 204 | 205 | * **Example:** 206 | 207 | ```js 208 | function foo({ x,y,z }) { return x + y + z; } 209 | 210 | var f = FPO.curry( {fn: foo, n: 3} ); 211 | 212 | f( {y: "b" } )()( {} )()( {x: "a", z: "!"} )( {z: "c"} ); 213 | // "abc" 214 | ``` 215 | 216 | * **See Also:** [`FPO.curryMultiple(..)`](#fpocurrymultiple), [`FPO.partial(..)`](#fpopartial), [`FPO.uncurry(..)`](#fpouncurry) 217 | 218 | ---- 219 | 220 | ### `FPO.curryMultiple(..)` 221 | 222 | ([back to top](#core-api)) 223 | 224 | Just like [`FPO.curry(..)`](#fpocurry), except each curried function allows multiple arguments instead of just one. 225 | 226 | * **Arguments:** 227 | - `fn`: function to curry 228 | - `n`: number of arguments to curry for 229 | 230 | * **Returns:** *function* 231 | 232 | * **Example:** 233 | 234 | ```js 235 | function foo({ x,y,z }) { return x + y + z; } 236 | 237 | var f = FPO.curry( {fn: foo, n: 3} ); 238 | 239 | f( {y: "b" } )()( {} )()( {x: "a", z: "!"} ); 240 | // "ab!" 241 | ``` 242 | 243 | * **See Also:** [`FPO.curry(..)`](#fpocurry), [`FPO.partial(..)`](#fpopartial), [`FPO.uncurry(..)`](#fpouncurry) 244 | 245 | ---- 246 | 247 | ### `FPO.filterIn(..)` 248 | 249 | ([back to top](#core-api)) 250 | 251 | Commonly known as `filter(..)`, produces a new list by calling a predicate function with each value in the original list. For each value, if the predicate function returns true (or truthy), the value is included in (aka, filtered into) the new list. Otherwise, the value is omitted. 252 | 253 | * **Arguments:** 254 | - `fn`: predicate function; called with `v` (value), `i` (index), and `arr` (array) named arguments 255 | - `arr`: array to filter against 256 | 257 | * **Returns:** *array* 258 | 259 | * **Example:** 260 | 261 | ```js 262 | function isOdd({ v }) { return v % 2 == 1; } 263 | 264 | var nums = [1,2,3,4,5]; 265 | 266 | FPO.filterIn( {fn: isOdd, arr: nums} ); 267 | // [1,3,5] 268 | ``` 269 | 270 | * **Aliases:** `FPO.filter(..)`, `FPO.keep(..)` 271 | 272 | * **See Also:** [`FPO.filterInObj(..)`](#fpofilterinobj), [`FPO.filterOut(..)`](#fpofilterout) 273 | 274 | ---- 275 | 276 | ### `FPO.filterInObj(..)` 277 | 278 | ([back to top](#core-api)) 279 | 280 | Produces a new object by calling a predicate function with each property value in the original object. For each value, if the predicate function returns true (or truthy), the value is included in (aka, filtered into) the new object at the same property name. Otherwise, the value is omitted. 281 | 282 | * **Arguments:** 283 | - `fn`: predicate function; called with `v` (value), `i` (property name), and `o` (object) named arguments 284 | - `o`: object to filter against 285 | 286 | * **Returns:** *object* 287 | 288 | * **Example:** 289 | 290 | ```js 291 | function isOdd({ v }) { return v % 2 == 1; } 292 | 293 | var nums = {a: 1, b: 2, c: 3, d: 4, e: 5}; 294 | 295 | FPO.filterInObj( {fn: isOdd, o: nums} ); 296 | // {a: 1, c: 3, e: 5} 297 | ``` 298 | 299 | * **Aliases:** `FPO.filterObj(..)`, `FPO.keepObj(..)` 300 | 301 | * **See Also:** [`FPO.filterIn(..)`](#fpofilterin), [`FPO.filterOutObj(..)`](#fpofilteroutobj) 302 | 303 | ---- 304 | 305 | ### `FPO.filterOut(..)` 306 | 307 | ([back to top](#core-api)) 308 | 309 | The inverse of [`FPO.filterIn(..)`](#fpofilterin), produces a new list by calling a predicate function with each value in the original list. For each value, if the predicate function returns true (or truthy), the value is omitted from (aka, filtered out of) the new list. Otherwise, the value is included. 310 | 311 | * **Arguments:** 312 | - `fn`: predicate function; called with `v` (value), `i` (index), and `arr` (array) named arguments 313 | - `arr`: array to filter against 314 | 315 | * **Returns:** *array* 316 | 317 | * **Example:** 318 | 319 | ```js 320 | function isOdd({ v }) { return v % 2 == 1; } 321 | 322 | var nums = [1,2,3,4,5]; 323 | 324 | FPO.filterOut( {fn: isOdd, arr: nums} ); 325 | // [2,4] 326 | ``` 327 | 328 | * **Aliases:** `FPO.reject(..)` 329 | 330 | * **See Also:** [`FPO.filterOutObj(..)`](#fpofilteroutobj), [`FPO.filterIn(..)`](#fpofilterin) 331 | 332 | ---- 333 | 334 | ### `FPO.filterOutObj(..)` 335 | 336 | ([back to top](#core-api)) 337 | 338 | The inverse of [`FPO.filterInObj(..)`](#fpofilterinobj), produces a new object by calling a predicate function with each property value in the original object. For each value, if the predicate function returns true (or truthy), the value is omitted from (aka, filtered out of) the new object. Otherwise, the value is included at the same property name. 339 | 340 | * **Arguments:** 341 | - `fn`: predicate function; called with `v` (value), `i` (property name), and `o` (object) named arguments 342 | - `o`: object to filter against 343 | 344 | * **Returns:** *object* 345 | 346 | * **Example:** 347 | 348 | ```js 349 | function isOdd({ v }) { return v % 2 == 1; } 350 | 351 | var nums = {a: 1, b: 2, c: 3, d: 4, e: 5}; 352 | 353 | FPO.filterOutObj( {fn: isOdd, o: nums} ); 354 | // {b: 2, d: 4} 355 | ``` 356 | 357 | * **Aliases:** `FPO.rejectObj(..)` 358 | 359 | * **See Also:** [`FPO.filterOut(..)`](#fpofilterout), [`FPO.filterInObj(..)`](#fpofilterinobj) 360 | 361 | ---- 362 | 363 | ### `FPO.flatMap(..)` 364 | 365 | ([back to top](#core-api)) 366 | 367 | Similar to [`FPO.map(..)`](#fpomap), produces a new list by calling a mapper function with each value in the original list. If the mapper function returns an array, this array is flattened (one level) into the overall array. 368 | 369 | * **Arguments:** 370 | - `fn`: mapper function; called with `v` (value), `i` (index), and `arr` (array) named arguments 371 | - `arr`: array to flat-map against 372 | 373 | * **Returns:** *array* 374 | 375 | * **Example:** 376 | 377 | ```js 378 | function splitChars({ v }) { return [...v]; } 379 | 380 | var words = ["hello","world"]; 381 | 382 | splitChars( {v: words[0]} ); 383 | // ["h","e","l","l","o"] 384 | 385 | FPO.map( {fn: splitChars, arr: words} ); 386 | // [["h","e","l","l","o"],["w","o","r","l","d"]] 387 | 388 | FPO.flatMap( {fn: splitChars, arr: words} ); 389 | // ["h","e","l","l","o","w","o","r","l","d"] 390 | ``` 391 | 392 | * **Aliases:** `FPO.chain(..)` 393 | 394 | * **See Also:** [`FPO.flatMapObj(..)`](#fpoflatmapobj), [`FPO.map(..)`](#fpomap), [`FPO.flatten(..)`](#fpoflatten) 395 | 396 | ---- 397 | 398 | ### `FPO.flatMapObj(..)` 399 | 400 | ([back to top](#core-api)) 401 | 402 | Similar to [`FPO.mapObj(..)`](#fpomapobj), produces a new object by calling a mapper function with each property value in the original object. If the mapper function returns an object, this object is flattened (one level) into the overall object, by copying its properties. 403 | 404 | * **Arguments:** 405 | - `fn`: mapper function; called with `v` (value), `i` (property name), and `o` (object) named arguments 406 | - `o`: object to flat-map against 407 | 408 | * **Returns:** *object* 409 | 410 | * **Example:** 411 | 412 | ```js 413 | function splitEvensInHalf({ v, i }) { 414 | if (v % 2 == 0) { 415 | return { [i]: v/2, [i+"_2"]: v/2 }; 416 | } 417 | return v; 418 | } 419 | 420 | var nums = {a: 1, b: 2, c: 3, d: 4}; 421 | 422 | splitEvensInHalf( {v: 3, i: "c"} ); 423 | // 3 424 | 425 | splitEvensInHalf( {v: 4, i: "d"} ); 426 | // {d: 2, d_2: 2} 427 | 428 | FPO.mapObj( {fn: splitEvensInHalf, o: nums} ); 429 | // {a: 1, b: {b: 1, b_2: 1}, c: 3, d: {d: 2, d_2: 2}} 430 | 431 | FPO.flatMapObj( {fn: splitEvensInHalf, o: nums} ); 432 | // {a: 1, b: 1, b_2: 1, c: 3, d: 2, d_2: 2}; 433 | ``` 434 | 435 | * **Aliases:** `FPO.chainObj(..)` 436 | 437 | * **See Also:** [`FPO.flatMap(..)`](#fpoflatmap), [`FPO.mapObj(..)`](#fpomapobj) 438 | 439 | ---- 440 | 441 | ### `FPO.flatten(..)` 442 | 443 | ([back to top](#core-api)) 444 | 445 | Flattens an array of nested arrays. Optionally, specify how many levels of nesting to flatten out. 446 | 447 | * **Arguments:** 448 | - `v`: array to flat-map against 449 | - `n`: (optional) the number of levels of nesting to flatten out; if omitted, defaults to Infinity (to flatten any nested depth). 450 | 451 | * **Returns:** *array* 452 | 453 | * **Example:** 454 | 455 | ```js 456 | var nums = [1,2,[3,4],[5,[6,7]]]; 457 | 458 | FPO.flatten( {v: nums} ); 459 | // [1,2,3,4,5,6,7] 460 | 461 | FPO.flatten( {v: nums, n: 1} ); 462 | // [1,2,3,4,5,[6,7]] 463 | 464 | FPO.flatten( {v: nums, n: 2} ); 465 | // [1,2,3,4,5,6,7] 466 | ``` 467 | 468 | * **See Also:** [`FPO.flatMap(..)`](#fpoflatmap) 469 | 470 | ---- 471 | 472 | ### `FPO.head(..)` 473 | 474 | ([back to top](#core-api)) 475 | 476 | Returns the element as accessed at index 0 of the value. 477 | 478 | * **Arguments:** 479 | - `v`: array, string, object 480 | 481 | * **Returns:** *any* 482 | 483 | * **Example:** 484 | 485 | ```js 486 | var nums = [1,2,3,4]; 487 | 488 | FPO.head( {v: nums} ); 489 | // 1 490 | 491 | FPO.head( {v: []} ); 492 | // undefined 493 | 494 | FPO.head( {v: "abc"} ); 495 | // "a" 496 | 497 | FPO.head( {v: {0: 42}} ); 498 | // 42 499 | ``` 500 | 501 | * **See Also:** [`FPO.tail(..)`](#fpotail) 502 | 503 | ---- 504 | 505 | ### `FPO.identity(..)` 506 | 507 | ([back to top](#core-api)) 508 | 509 | Returns the value given to it. Useful as a default placeholder for certain opertaions (i.e., composition, reduction). 510 | 511 | * **Arguments:** 512 | - `v`: value to return 513 | 514 | * **Returns:** *-any-* 515 | 516 | * **Example:** 517 | 518 | ```js 519 | FPO.identity( {v: 42} ); // 42 520 | ``` 521 | 522 | * **See Also:** [`FPO.constant(..)`](#fpoconstant) 523 | 524 | ---- 525 | 526 | ### `FPO.map(..)` 527 | 528 | ([back to top](#core-api)) 529 | 530 | Produces a new list by calling a mapper function with each value in the original list. The value the mapper function returns is inserted in the new list at that same position. The new list will always be the same length as the original list. 531 | 532 | * **Arguments:** 533 | - `fn`: mapper function; called with `v` (value), `i` (index), and `arr` (array) named arguments 534 | - `arr`: array to map against 535 | 536 | * **Returns:** *array* 537 | 538 | * **Example:** 539 | 540 | ```js 541 | function double({ v }) { return v * 2; } 542 | 543 | var nums = [1,2,3,4,5]; 544 | 545 | FPO.map( {fn: double, arr: nums} ); 546 | // [2,4,6,8,10] 547 | ``` 548 | 549 | * **See Also:** [`FPO.mapObj(..)`](#fpomapobj), [`FPO.flatMap(..)`](#fpoflatmap) 550 | 551 | ---- 552 | 553 | ### `FPO.mapObj(..)` 554 | 555 | ([back to top](#core-api)) 556 | 557 | Produces a new object by calling a mapper function with each property value in the original object. The value the mapper function returns is inserted in the new object at that same property name. The new object will always have the same number of properties as the original object. 558 | 559 | * **Arguments:** 560 | - `fn`: mapper function; called with `v` (value), `i` (property name), and `o` (object) named arguments 561 | - `o`: object to map against 562 | 563 | * **Returns:** *object* 564 | 565 | * **Example:** 566 | 567 | ```js 568 | function double({ v }) { return v * 2; } 569 | 570 | var nums = {a: 1, b: 2, c: 3, d: 4, e: 5}; 571 | 572 | FPO.mapObj( {fn: double, o: nums} ); 573 | // {a: 2, b: 4, c: 6, d: 8, e: 10}; 574 | ``` 575 | 576 | * **See Also:** [`FPO.map(..)`](#fpomap) 577 | 578 | ---- 579 | 580 | ### `FPO.memoize(..)` 581 | 582 | ([back to top](#core-api)) 583 | 584 | For performance optimization reasons, wraps a function such that it remembers each set of arguments passed to it, associated with that underlying return value. If the wrapped function is called subsequent times with the same set of arguments, the cached return value is returned instead of being recomputed. Each wrapped function instance has its own separate cache, even if wrapping the same original function multiple times. 585 | 586 | A set of arguments is "remembered" by being hashed to a string value to use as a cache key. This hashing is done internally with `JSON.stringify(..)`, which is fast and works with many common JS value types. However, this hashing is by no means bullet-proof for all types, and does not guarantee collision-free. **Use caution:** generally, you should only use primitives (number, string, boolean, null, and undefined) or simple objects (object, array) as arguments. If you use objects, always make sure to list properties in the same order to ensure proper hashing. 587 | 588 | By default, the function's arity (`fn.length`) will be detected as `n`. However, in JS certain techniques thwart this detection, such as the use of default parameters or parameter destructuring. Make sure to specify the correct `n` if detection is uncertain or unreliable. 589 | 590 | Unary functions (single argument; `n` of `1`) with a primitive argument are the fastest for memoization, so if possible, try to design functions that way. In these cases, specifying `n` as `1` will help ensure the best possible performance. 591 | 592 | **Warning:** Be aware that if `1` is initially specified (or detected) for `n`, additional arguments later passed to the wrapped function are **not considered** in the memoization hashing, though they will still be passed to the underlying function as-is. This may cause unexpected results (false-positives on cache hits); always make sure `n` matches the expected number of arguments. 593 | 594 | * **Arguments:** 595 | - `fn`: function to wrap 596 | - `n`: number of arguments to memoize; if omitted, tries to detect the arity (`fn.length`) to use. 597 | 598 | * **Returns:** *array* 599 | 600 | * **Example:** 601 | 602 | ```js 603 | function sum(x,y) { console.log( "sum called!" ); return x + y; } 604 | function mult({x, y}) { console.log( "mult called!" ); return x * y; } 605 | 606 | var A = FPO.memoize( {fn: sum} ); 607 | var B = FPO.memoize( {fn: sum, n: 1} ); // be careful here! 608 | var C = FPO.memoize( {fn: mult, n: 1} ); 609 | 610 | A( 2, 3 ); 611 | // sum called! 612 | // 5 613 | 614 | A( 2, 3 ); // no need to re-compute, value pulled from cache 615 | // 5 616 | 617 | B( 2, 3 ); // different instance, separate cache, re-computed 618 | // sum called! 619 | // 5 620 | 621 | B( 2, 100 ); // oops, memoization fail here! 622 | // 5 623 | 624 | C( {x: 3, y: 4} ); 625 | // mult called! 626 | // 12 627 | 628 | C( {x: 3, y: 4} ); 629 | // 12 630 | 631 | C( {y: 4, x: 3} ); // oops, cache hashing is different 632 | // mult called! 633 | // 12 634 | ``` 635 | 636 | ---- 637 | 638 | ### `FPO.nAry(..)` 639 | 640 | ([back to top](#core-api)) 641 | 642 | Wraps a function to restrict its inputs to only the named arguments as specified. 643 | 644 | * **Arguments:** 645 | - `fn`: function to wrap 646 | - `props`: array of property names to allow as named arguments; if empty, produces a "nullary" function -- won't receive any arguments. 647 | 648 | * **Returns:** *function* 649 | 650 | * **Example:** 651 | 652 | ```js 653 | function foo(argsObj) { return argsObj; } 654 | 655 | var f = FPO.nAry( {fn: foo, props: ["x","y","z"]} ); 656 | 657 | f( {x: 1, y: 2, z: 3, w: 4} ); 658 | // { x:1, y:2, z:3 } 659 | ``` 660 | 661 | * **See Also:** [`FPO.binary(..)`](#fpobinary), [`FPO.unary(..)`](#fpounary) 662 | 663 | ---- 664 | 665 | ### `FPO.partial(..)` 666 | 667 | ([back to top](#core-api)) 668 | 669 | Wraps a function with a new function that already has some of the arguments pre-specified, and is waiting for the rest of them on the next call. Unlike [`FPO.curry(..)`](#fpocurry), you must specify all the remaining arguments on the next call of the partially-applied function. 670 | 671 | With traditional FP libraries, `partial(..)` works in left-to-right order (as does `FPO.std.partial(..)`). That's why typically you also need a `FPO.std.partialRight(..)` if you want to partially-apply from the opposite direction. 672 | 673 | However, using named arguments style -- after all, that is the whole point of FPO! -- order doesn't matter. For familiarity sake, `FPO.partialRight(..)` is provided, but it's just an alias to `FPO.partial(..)`. 674 | 675 | * **Arguments:** 676 | - `fn`: function to partially-apply 677 | - `args`: object containing the arguments to apply now 678 | 679 | * **Returns:** *function* 680 | 681 | * **Example:** 682 | 683 | ```js 684 | function foo({ x,y,z }) { return x + y + z; } 685 | 686 | var f = FPO.partial( {fn: foo, args: {x: "a"}} ); 687 | 688 | f( {y: "b", z: "!"} ); 689 | // "ab!" 690 | ``` 691 | 692 | * **Aliases:** `FPO.partialRight(..)` 693 | 694 | * **See Also:** [`FPO.curry(..)`](#fpocurry), [`FPO.curryMultiple(..)`](#fpocurrymultiple) 695 | 696 | ---- 697 | 698 | ### `FPO.pick(..)` 699 | 700 | ([back to top](#core-api)) 701 | 702 | Returns a new object with only the specified properties from the original object. Includes only properties from the original object. 703 | 704 | * **Arguments:** 705 | - `v`: object to pick properties from 706 | - `props`: array of property names to pick from the object; if a property does not exist on the original object, it **is not** added to the new object, unlike [`FPO.pickAll(..)`](#fpopickall). 707 | 708 | * **Returns:** *object* 709 | 710 | * **Example:** 711 | 712 | ```js 713 | var obj = { x: 1, y: 2, z: 3 }; 714 | 715 | FPO.pick( {v: obj, props: ["x","y","w"]} ); 716 | // { x:1, y:2 } 717 | ``` 718 | 719 | * **See Also:** [`FPO.pickAll(..)`](#fpopickall), [`FPO.prop(..)`](#fpoprop) 720 | 721 | ---- 722 | 723 | ### `FPO.pickAll(..)` 724 | 725 | ([back to top](#core-api)) 726 | 727 | Returns a new object with only the specified properties from the original object. Includes all specified properties. 728 | 729 | * **Arguments:** 730 | - `v`: object to pick properties from 731 | - `props`: array of property names to pick from the object; even if a property does not exist on the original object, it **is** still added to the new object with an `undefined` value, unlike [`FPO.pick(..)`](#fpopick). 732 | 733 | * **Returns:** *object* 734 | 735 | * **Example:** 736 | 737 | ```js 738 | var obj = { x: 1, y: 2, z: 3 }; 739 | 740 | FPO.pickAll( {v: obj, props: ["x","y","w"]} ); 741 | // { x:1, y:2, w:undefined } 742 | ``` 743 | 744 | * **See Also:** [`FPO.pick(..)`](#fpopick) 745 | 746 | ---- 747 | 748 | ### `FPO.pipe(..)` 749 | 750 | ([back to top](#core-api)) 751 | 752 | Produces a new function that's the composition of a list of functions. Functions are composed left-to-right (unlike [`FPO.compose(..)`](#fpocompose)) from the array. 753 | 754 | * **Arguments:** 755 | - `fns`: array of functions 756 | 757 | * **Returns:** *function* 758 | 759 | * **Example:** 760 | 761 | ```js 762 | var f = FPO.pipe( {fns: [ 763 | ({v}) => v + "3", 764 | ({v}) => v + "2", 765 | ({v}) => v + "1" 766 | ]} ); 767 | 768 | f( {v: "4"} ); // "4321" 769 | ``` 770 | 771 | * **Aliases:** `FPO.flow(..)`, `FPO.sequence(..)` 772 | 773 | * **See Also:** [`FPO.compose(..)`](#fpocompose) 774 | 775 | ---- 776 | 777 | ### `FPO.prop(..)` 778 | 779 | ([back to top](#core-api)) 780 | 781 | Extracts a property's value from an object. 782 | 783 | * **Arguments:** 784 | - `v`: object to pull the property value from 785 | - `prop`: property name to pull from the object 786 | 787 | * **Returns:** *-any-* 788 | 789 | * **Example:** 790 | 791 | ```js 792 | var obj = { x: 1, y: 2, z: 3 }; 793 | 794 | FPO.prop( {v: obj, prop: "y"} ); // 2 795 | ``` 796 | 797 | * **See Also:** [`FPO.pick(..)`](#fpopick), [`FPO.setProp(..)`](#fposetprop) 798 | 799 | ---- 800 | 801 | ### `FPO.reassoc(..)` 802 | 803 | ([back to top](#core-api)) 804 | 805 | Like a mixture between [`FPO.pick(..)`](#fpopick) and [`FPO.setProp(..)`](#fposetprop), creates a new object that has properties remapped from original names to new names. Any properties present on the original object that aren't remapped are copied with the same name. 806 | 807 | * **Arguments:** 808 | - `props`: object whose key/value pairs are `sourceProp: targetProp` remappings 809 | - `v`: object to remap properties from 810 | 811 | * **Returns:** *object* 812 | 813 | * **Example:** 814 | 815 | ```js 816 | var obj = { x: 1, y: 2, z: 3 }; 817 | 818 | FPO.reassoc( {v: obj, props: {x: "A", z: "__z__"}} ); 819 | // { A: 1, __z__: 3, y: 2} 820 | ``` 821 | 822 | * **See Also:** [`FPO.pick(..)`](#fpopick), [`FPO.setProp(..)`](#fposetprop) 823 | 824 | ---- 825 | 826 | ### `FPO.reduce(..)` 827 | 828 | ([back to top](#core-api)) 829 | 830 | Processes a list from left-to-right (unlike [`FPO.reduceRight(..)`](#fporeduceright)), successively combining (aka "reducing", "folding") two values into one, until the entire list has been reduced to a single value. An initial value for the reduction can optionally be provided. 831 | 832 | * **Arguments:** 833 | - `fn`: reducer function; called with `acc` (accumulator), `v` (value), `i` (index), and `arr` (array) named arguments 834 | - `arr`: array to reduce 835 | - `v`: (optional) initial value to use for the reduction; if provided, the first reduction will pass to the reducer the initial value as the `acc` and the first value from the array as `v`. Otherwise, the first reduction has the first value of the array as `acc` and the second value of the array as `v`. 836 | 837 | * **Returns:** *-any-* 838 | 839 | * **Example:** 840 | 841 | ```js 842 | function strConcat({ acc, v }) { return acc + v; } 843 | 844 | var vowels = ["a","e","i","o","u","y"]; 845 | 846 | FPO.reduce( {fn: strConcat, arr: vowels} ); 847 | // "aeiouy" 848 | 849 | FPO.reduce( {fn: strConcat, arr: vowels, v: "vowels: "} ); 850 | // "vowels: aeiouy" 851 | ``` 852 | 853 | * **Aliases:** `FPO.fold(..)`, `FPO.foldL(..)` 854 | 855 | * **See Also:** [`FPO.reduceObj(..)`](#fporeduceobj), [`FPO.reduceRight(..)`](#fporeduceright) 856 | 857 | ---- 858 | 859 | ### `FPO.reduceObj(..)` 860 | 861 | ([back to top](#core-api)) 862 | 863 | Processes an object's properties (in enumeration order), successively combining (aka "reducing", "folding") two values into one, until all the object's properties have been reduced to a single value. An initial value for the reduction can optionally be provided. 864 | 865 | **Note:** Enumeration order of properties is not strictly guaranteed cross-environment. However, it's generally reliable as the order that properties were listed/added to the object in its definition. 866 | 867 | * **Arguments:** 868 | - `fn`: reducer function; called with `acc` (accumulator), `v` (value), `i` (property name), and `o` (object) named arguments 869 | - `o`: object to reduce 870 | - `v`: (optional) initial value to use for the reduction; if provided, the first reduction will pass to the reducer the initial value as the `acc` and the first property value (in enumeration order) from the object as `v`. Otherwise, the first reduction has the first property value (in enumeration order) of the object as `acc` and the second property value (in enumeration order) of the object as `v`. 871 | 872 | * **Returns:** *-any-* 873 | 874 | * **Example:** 875 | 876 | ```js 877 | function strConcat({ acc, v }) { return acc + v; } 878 | 879 | var vowels = {a: "a", b: "e", c: "i", d: "o", e: "u", f: "y"}; 880 | 881 | FPO.reduceObj( {fn: strConcat, o: vowels} ); 882 | // "aeiouy" 883 | 884 | FPO.reduceObj( {fn: strConcat, o: vowels, v: "vowels: "} ); 885 | // "vowels: aeiouy" 886 | ``` 887 | 888 | * **Aliases:** `FPO.foldObj(..)` 889 | 890 | * **See Also:** [`FPO.reduce(..)`](#fporeduce) 891 | 892 | ---- 893 | 894 | ### `FPO.reduceRight(..)` 895 | 896 | ([back to top](#core-api)) 897 | 898 | Processes a list from right-to-left (unlike [`FPO.reduce(..)`](#fporeduce)), successively combining (aka "reducing", "folding") two values into one, until the entire list has been reduced to a single value. 899 | 900 | An initial value for the reduction can optionally be provided. If the array is empty, the initial value is returned (or `undefined` if it was omitted). 901 | 902 | * **Arguments:** 903 | - `fn`: reducer function; called with `acc` (accumulator), `v` (value), `i` (index), and `arr` (array) named arguments 904 | - `arr`: array to reduce 905 | - `v`: (optional) initial value to use for the reduction; if provided, the first reduction will pass to the reducer the initial value as the `acc` and the first value from the array as `v`. Otherwise, the first reduction has the first value of the array as `acc` and the second value of the array as `v`. 906 | 907 | * **Returns:** *-any-* 908 | 909 | * **Example:** 910 | 911 | ```js 912 | function strConcat({ acc, v }) { return acc + v; } 913 | 914 | var vowels = ["a","e","i","o","u","y"]; 915 | 916 | FPO.reduceRight( {fn: strConcat, arr: vowels} ); 917 | // "yuoiea" 918 | 919 | FPO.reduceRight( {fn: strConcat, arr: vowels, v: "vowels: "} ); 920 | // "vowels: yuoiea" 921 | ``` 922 | 923 | * **Aliases:** `FPO.foldR(..)` 924 | 925 | * **See Also:** [`FPO.reduce(..)`](#fporeduce) 926 | 927 | ---- 928 | 929 | ### `FPO.remap(..)` 930 | 931 | ([back to top](#core-api)) 932 | 933 | Remaps the expected named arguments of a function. This is useful to adapt a function to be used if the arguments passed in will be different than what the function expects. 934 | 935 | A common usecase will be to adapt a function so it's suitable for use as a mapper/predicate/reducer function, or for composition. 936 | 937 | * **Arguments:** 938 | - `fn`: function to remap 939 | - `args`: object whose key/value pairs represent the `origArgName: newArgName` mappings 940 | 941 | * **Returns:** *function* 942 | 943 | * **Example:** 944 | 945 | ```js 946 | function double({ x }) { return x * 2; } 947 | function increment({ y }) { return y + 1; } 948 | function div3({ z }) { return z / 3; } 949 | 950 | var f = FPO.remap( {fn: double, args: {x: "v"}} ); 951 | var g = FPO.remap( {fn: increment, args: {y: "v"}} ); 952 | var h = FPO.remap( {fn: div3, args: {z: "v"}} ); 953 | 954 | f( {v: 3} ); 955 | // 6 956 | 957 | FPO.map( {fn: g, arr: [5,10,15,20,25]} ); 958 | // [6,11,16,21,26] 959 | 960 | var m = FPO.compose( {fns: [h,g,f]} ); 961 | m( {v: 4} ); 962 | // 3 963 | 964 | FPO.map( {fn: m, arr: [1,4,7,10,13]} ); 965 | // [1,3,5,7,9] 966 | ``` 967 | 968 | * **See Also:** [`FPO.reassoc(..)`](#fporeassoc) 969 | 970 | ---- 971 | 972 | ### `FPO.setProp(..)` 973 | 974 | ([back to top](#core-api)) 975 | 976 | Creates a shallow clone of an object, assigning the specified property value to the new object. 977 | 978 | * **Arguments:** 979 | - `o`: (optional) object to clone; if omitted, defaults to a new empty object 980 | - `prop`: property name where to set the value on the new object 981 | - `v`: value 982 | 983 | * **Returns:** *object* 984 | 985 | * **Example:** 986 | 987 | ```js 988 | var obj = { x: 1, y: 2, z: 3 }; 989 | 990 | FPO.setProp( {o: obj, prop: "w", v: 4} ); 991 | // { x:1, y:2, z:3, w:4 } 992 | 993 | obj; 994 | // { x:1, y:2, z:3 } 995 | ``` 996 | 997 | * **Aliases:** `FPO.assoc(..)` 998 | 999 | * **See Also:** [`FPO.prop(..)`](#fpoprop) 1000 | 1001 | ---- 1002 | 1003 | ### `FPO.tail(..)` 1004 | 1005 | ([back to top](#core-api)) 1006 | 1007 | Returns everything else in the value except the element as accessed at index 0; basically the inverse of [`FPO.head(..)`](#fpohead). 1008 | 1009 | * **Arguments:** 1010 | - `v`: array, string, object 1011 | 1012 | * **Returns:** *any* 1013 | 1014 | * **Example:** 1015 | 1016 | ```js 1017 | var nums = [1,2,3,4]; 1018 | 1019 | FPO.tail( {v: nums} ); 1020 | // [2,3,4] 1021 | 1022 | FPO.tail( {v: []} ); 1023 | // undefined 1024 | 1025 | FPO.tail( {v: "abc"} ); 1026 | // "bc" 1027 | 1028 | FPO.tail( {v: {0: 42, 1: 10}} ); 1029 | // {1: 10} 1030 | ``` 1031 | 1032 | * **See Also:** [`FPO.head(..)`](#fpohead) 1033 | 1034 | ---- 1035 | 1036 | ### `FPO.take(..)` 1037 | 1038 | ([back to top](#core-api)) 1039 | 1040 | Returns the specified number of elements from the value, starting from the beginning. 1041 | 1042 | * **Arguments:** 1043 | - `v`: array / string 1044 | - `n`: number of elements to take from the beginning of the value; if omitted, defaults to `1`. 1045 | 1046 | * **Returns:** array / string 1047 | 1048 | * **Example:** 1049 | 1050 | ```js 1051 | var nums = [1,2,3,4]; 1052 | 1053 | FPO.take( {v: nums, n: 2} ); 1054 | // [1,2] 1055 | 1056 | FPO.take( {v: nums} ); 1057 | // [1] 1058 | 1059 | FPO.take( {v: "abc", n: 2} ); 1060 | // "ab" 1061 | 1062 | FPO.take( {v: null} ); 1063 | // [] 1064 | ``` 1065 | 1066 | * **See Also:** [`FPO.head(..)`](#fpohead) 1067 | 1068 | ---- 1069 | 1070 | ### `FPO.trampoline(..)` 1071 | 1072 | ([back to top](#core-api)) 1073 | 1074 | Wraps a continuation-returning recursive function in another function that will run it until it no longer returns another continuation function. Trampolines are an alternative to tail calls. 1075 | 1076 | * **Arguments:** 1077 | - `fn`: function to run 1078 | 1079 | * **Returns:** *function* 1080 | 1081 | * **Example:** 1082 | 1083 | ```js 1084 | function sum(total,x) { 1085 | if (x <= 1) return total + x; 1086 | return () => sum( total + x, x - 1 ); 1087 | } 1088 | 1089 | FPO.trampoline( {fn: sum} )( 0, 5 ); 1090 | // 15 1091 | ``` 1092 | 1093 | ---- 1094 | 1095 | ### `FPO.transducers.array(..)` 1096 | 1097 | ([back to top](#core-api)) 1098 | 1099 | A reducer function. For transducing purposes, a combination function that takes an array and a value, and mutates the array by pushing the value onto the end of it. The mutated array is returned. 1100 | 1101 | **This function has side-effects**, for performance reasons. It should be used with caution. 1102 | 1103 | * **Arguments:** 1104 | - `acc`: acculumator 1105 | - `v`: value 1106 | 1107 | * **Returns:** *array* 1108 | 1109 | * **Example:** 1110 | 1111 | ```js 1112 | var arr = [1,2,3]; 1113 | 1114 | FPO.transducers.array( {acc: arr, v: 4} ); 1115 | // [1,2,3,4] 1116 | 1117 | arr; 1118 | // [1,2,3,4] <-- was mutated as a side-effect! 1119 | ``` 1120 | 1121 | * **See Also:** [`FPO.transducers.booleanAnd(..)`](#fpotransducersbooleanand), 1122 | * [`FPO.transducers.booleanOr(..)`](#fpotransducersbooleanor), [`FPO.transducers.default(..)`](#fpotransducersdefault), [`FPO.transducers.number(..)`](#fpotransducersnumber), [`FPO.transducers.string(..)`](#fpotransducersstring) 1123 | 1124 | ---- 1125 | 1126 | ### `FPO.transducers.booleanAnd(..)` 1127 | 1128 | ([back to top](#core-api)) 1129 | 1130 | A reducer function. For transducing purposes, a combination function that takes two booleans and *AND*s them together. The result is the logical *AND* of the two values. 1131 | 1132 | * **Arguments:** 1133 | - `acc`: acculumator 1134 | - `v`: value 1135 | 1136 | * **Returns:** *true/false* 1137 | 1138 | * **Example:** 1139 | 1140 | ```js 1141 | FPO.transducers.booleanAnd( {acc: true, v: true} ); 1142 | // true 1143 | 1144 | FPO.transducers.booleanAnd( {acc: false, v: true} ); 1145 | // false 1146 | ``` 1147 | 1148 | * **Aliases:** `FPO.transducers.boolean(..)` 1149 | 1150 | * **See Also:** [`FPO.transducers.array(..)`](#fpotransducersarray), 1151 | * [`FPO.transducers.booleanOr(..)`](#fpotransducersbooleanor), [`FPO.transducers.default(..)`](#fpotransducersdefault), [`FPO.transducers.number(..)`](#fpotransducersnumber), [`FPO.transducers.string(..)`](#fpotransducersstring) 1152 | 1153 | ---- 1154 | 1155 | ### `FPO.transducers.booleanOr(..)` 1156 | 1157 | ([back to top](#core-api)) 1158 | 1159 | A reducer function. For transducing purposes, a combination function that takes two booleans and *OR*s them together. The result is the logical *OR* of the two values. 1160 | 1161 | * **Arguments:** 1162 | - `acc`: acculumator 1163 | - `v`: value 1164 | 1165 | * **Returns:** *true/false* 1166 | 1167 | * **Example:** 1168 | 1169 | ```js 1170 | FPO.transducers.booleanOr( {acc: false, v: true} ); 1171 | // true 1172 | 1173 | FPO.transducers.booleanOr( {acc: false, v: false} ); 1174 | // false 1175 | ``` 1176 | 1177 | * **See Also:** [`FPO.transducers.array(..)`](#fpotransducersarray), 1178 | * [`FPO.transducers.booleanAnd(..)`](#fpotransducersbooleanand), [`FPO.transducers.default(..)`](#fpotransducersdefault), [`FPO.transducers.number(..)`](#fpotransducersnumber), [`FPO.transducers.string(..)`](#fpotransducersstring) 1179 | 1180 | ---- 1181 | 1182 | ### `FPO.transducers.default(..)` 1183 | 1184 | ([back to top](#core-api)) 1185 | 1186 | A reducer function. For transducing purposes, a combination function that's a default placeholder. It returns only the `acc` that's passed to it. The behavior here is almost the same as [`FPO.identity(..)`](#fpoidentity), except that returns `acc` instead of `v`. 1187 | 1188 | * **Arguments:** 1189 | - `acc`: acculumator 1190 | - `v`: value 1191 | 1192 | * **Returns:** *-any-* 1193 | 1194 | * **Example:** 1195 | 1196 | ```js 1197 | FPO.transducers.default( {acc: 3, v: 1} ); 1198 | // 3 1199 | ``` 1200 | 1201 | * **See Also:** [`FPO.transducers.array(..)`](#fpotransducersarray), 1202 | * [`FPO.transducers.booleanAnd(..)`](#fpotransducersbooleanand), [`FPO.transducers.booleanOr(..)`](#fpotransducersbooleanOr), [`FPO.transducers.number(..)`](#fpotransducersnumber), [`FPO.transducers.string(..)`](#fpotransducersstring), [`FPO.identity(..)`](#fpoidentity) 1203 | 1204 | ---- 1205 | 1206 | ### `FPO.transducers.filter(..)` 1207 | 1208 | ([back to top](#core-api)) 1209 | 1210 | For transducing purposes, wraps a predicate function as a filter-transducer. Typically, this filter-transducer is then composed with other filter-transducers and/or map-transducers. The resulting transducer is then passed to [`FPO.transducers.transduce(..)`](#fpotransducerstransduce). 1211 | 1212 | The filter-transducer is not a reducer itself; it's expecting a combination function (reducer), which will then produce a filter-reducer. So alternately, you can manually create the filter-reducer and use it directly with a regular [`FPO.reduce(..)`](#fporeduce) reduction. 1213 | 1214 | * **Arguments:** 1215 | - `fn`: predicate function 1216 | 1217 | * **Returns:** *function* 1218 | 1219 | * **Example:** 1220 | 1221 | ```js 1222 | function isOdd({ v }) { return v % 2 == 1; } 1223 | function arrayPush({ acc, v }) { acc.push( v ); return acc; } 1224 | 1225 | var nums = [1,2,3,4,5]; 1226 | 1227 | var filterTransducer = FPO.transducers.filter( {fn: isOdd} ); 1228 | 1229 | FPO.transducers.transduce( 1230 | {fn: filterTransducer, co: arrayPush, v: [], arr: nums} 1231 | ); 1232 | // [1,3,5] 1233 | 1234 | // ****************** 1235 | 1236 | var filterReducer = filterTransducer( {v: arrayPush} ); 1237 | 1238 | filterReducer( {acc: [], v: 3} ); 1239 | // [3] 1240 | 1241 | filterReducer( {acc: [], v: 4} ); 1242 | // [] 1243 | 1244 | FPO.reduce( {fn: filterReducer, v: [], arr: nums} ); 1245 | // [1,3,5] 1246 | ``` 1247 | 1248 | * **See Also:** [`FPO.transducers.map(..)`](#fpotransducersmap) 1249 | 1250 | ---- 1251 | 1252 | ### `FPO.transducers.into(..)` 1253 | 1254 | ([back to top](#core-api)) 1255 | 1256 | Selects an appropriate combination function (reducer) based on the provided initial value. Then runs [`FPO.transducers.transduce(..)`](#fpotransducerstransduce) under the covers. 1257 | 1258 | Detects initial values of `boolean`, `number`, `string`, and `array` types, and dispatches to the appropriate combination function accordingly ([`FPO.transducers.number(..)`](#fpotransducersnumber), etc). **Note:** A `boolean` initial value selects [`FPO.transducers.booleanAnd(..)`](#fpotransducersboooleanand). 1259 | 1260 | **Note:** When composing transducers, the effective order of operations is reversed from normal composition. Instead of expecting composition to be right-to-left, the effective order will be left-to-right (see below). 1261 | 1262 | * **Arguments:** 1263 | - `fn`: transducer function 1264 | - `v`: initial value for the reduction; also used to select the appropriate combination function (reducer) for the transducing. 1265 | - `arr`: the list for the reduction 1266 | 1267 | * **Returns:** *-any-* 1268 | 1269 | * **Example:** 1270 | 1271 | ```js 1272 | function double({ v }) { return v * 2; } 1273 | function isOdd({ v }) { return v % 2 == 1; } 1274 | 1275 | var nums = [1,2,3,4,5]; 1276 | 1277 | var transducer = FPO.compose( {fns: [ 1278 | FPO.transducers.filter( {fn: isOdd} ), 1279 | FPO.transducers.map( {fn: double} ) 1280 | ]} ); 1281 | 1282 | FPO.transducers.into( {fn: transducer, v: [], arr: nums} ); 1283 | // [2,6,10] 1284 | 1285 | FPO.transducers.into( {fn: transducer, v: 0, arr: nums} ); 1286 | // 18 1287 | 1288 | FPO.transducers.into( {fn: transducer, v: "", arr: nums} ); 1289 | // "2610" 1290 | ``` 1291 | 1292 | * **See Also:** [`FPO.transducers.transduce(..)`](#fpotransducerstransduce) 1293 | 1294 | ---- 1295 | 1296 | ### `FPO.transducers.map(..)` 1297 | 1298 | ([back to top](#core-api)) 1299 | 1300 | For transducing purposes, wraps a mapper function as a map-transducer. Typically, this map-transducer is then composed with other filter-transducers and/or map-transducers. The resulting transducer is then passed to [`FPO.transducers.transduce(..)`](#fpotransducerstransduce). 1301 | 1302 | The map-transducer is not a reducer itself; it's expecting a combination function (reducer), which will then produce a filter-reducer. So alternately, you can manually create the map-reducer and use it directly with a regular [`FPO.reduce(..)`](#fporeduce) reduction. 1303 | 1304 | * **Arguments:** 1305 | - `fn`: mapper function 1306 | 1307 | * **Returns:** *function* 1308 | 1309 | * **Example:** 1310 | 1311 | ```js 1312 | function double({ v }) { return v * 2; } 1313 | function arrayPush({ acc, v }) { acc.push( v ); return acc; } 1314 | 1315 | var nums = [1,2,3,4,5]; 1316 | 1317 | var mapTransducer = FPO.transducers.map( {fn: double} ); 1318 | 1319 | FPO.transducers.transduce( 1320 | {fn: mapTransducer, co: arrayPush, v: [], arr: nums} 1321 | ); 1322 | // [2,4,6,8,10] 1323 | 1324 | // ****************** 1325 | 1326 | var mapReducer = mapTransducer( {v: arrayPush} ); 1327 | 1328 | mapReducer( {acc: [], v: 3} ); 1329 | // [6] 1330 | 1331 | FPO.reduce( {fn: mapReducer, v: [], arr: nums} ); 1332 | // [2,4,6,8,10] 1333 | ``` 1334 | 1335 | * **See Also:** [`FPO.transducers.filter(..)`](#fpotransducersfilter) 1336 | 1337 | ---- 1338 | 1339 | ### `FPO.transducers.number(..)` 1340 | 1341 | ([back to top](#core-api)) 1342 | 1343 | A reducer function. For transducing purposes, a combination function that adds together the two numbers passed into it. The result is the sum. 1344 | 1345 | * **Arguments:** 1346 | - `acc`: acculumator 1347 | - `v`: value 1348 | 1349 | * **Returns:** *number* 1350 | 1351 | * **Example:** 1352 | 1353 | ```js 1354 | FPO.transducers.number( {acc: 3, v: 4} ); 1355 | // 7 1356 | ``` 1357 | 1358 | * **See Also:** [`FPO.transducers.array(..)`](#fpotransducersarray), 1359 | * [`FPO.transducers.booleanAnd(..)`](#fpotransducersbooleanand), [`FPO.transducers.booleanOr(..)`](#fpotransducersbooleanOr), [`FPO.transducers.default(..)`](#fpotransducersdefault), [`FPO.transducers.string(..)`](#fpotransducersstring) 1360 | 1361 | ---- 1362 | 1363 | ### `FPO.transducers.string(..)` 1364 | 1365 | ([back to top](#core-api)) 1366 | 1367 | A reducer function. For transducing purposes, a combination function that concats the two strings passed into it. The result is the concatenation. 1368 | 1369 | * **Arguments:** 1370 | - `acc`: acculumator 1371 | - `v`: value 1372 | 1373 | * **Returns:** *string* 1374 | 1375 | * **Example:** 1376 | 1377 | ```js 1378 | FPO.transducers.string( {acc: "hello", v: "world"} ); 1379 | // "helloworld" 1380 | ``` 1381 | 1382 | * **See Also:** [`FPO.transducers.array(..)`](#fpotransducersarray), 1383 | * [`FPO.transducers.booleanAnd(..)`](#fpotransducersbooleanand), [`FPO.transducers.booleanOr(..)`](#fpotransducersbooleanOr), [`FPO.transducers.default(..)`](#fpotransducersdefault), [`FPO.transducers.number(..)`](#fpotransducersnumber) 1384 | 1385 | ---- 1386 | 1387 | ### `FPO.transducers.transduce(..)` 1388 | 1389 | ([back to top](#core-api)) 1390 | 1391 | Produces a reducer from a specified transducer and combination function. Then runs a reduction on a list, using that reducer, starting with the specified initial value. 1392 | 1393 | **Note:** When composing transducers, the effective order of operations is reversed from normal composition. Instead of expecting composition to be right-to-left, the effective order will be left-to-right (see below). 1394 | 1395 | * **Arguments:** 1396 | - `fn`: transducer function 1397 | - `co`: combination function for the transducer 1398 | - `v`: initial value for the reduction 1399 | - `arr`: the list for the reduction 1400 | 1401 | * **Returns:** *-any-* 1402 | 1403 | * **Example:** 1404 | 1405 | ```js 1406 | function double({ v }) { return v * 2; } 1407 | function isOdd({ v }) { return v % 2 == 1; } 1408 | function arrayPush({ acc, v }) { acc.push( v ); return acc; } 1409 | 1410 | var nums = [1,2,3,4,5]; 1411 | 1412 | var transducer = FPO.compose( {fns: [ 1413 | FPO.transducers.filter( {fn: isOdd} ), 1414 | FPO.transducers.map( {fn: double} ) 1415 | ]} ); 1416 | 1417 | FPO.transducers.transduce( 1418 | {fn: transducer, co: arrayPush, v: [], arr: nums} 1419 | ); 1420 | // [2,6,10] 1421 | ``` 1422 | 1423 | * **See Also:** [`FPO.transducers.into(..)`](#fpotransducersinto) 1424 | 1425 | ---- 1426 | 1427 | ### `FPO.unapply(..)` 1428 | 1429 | ([back to top](#core-api)) 1430 | 1431 | Wraps a function to gather individual positional arguments into an object argument. 1432 | 1433 | * **Arguments:** 1434 | - `fn`: function to wrap 1435 | - `props`: list of property names (strings) to indicate the order to gather individual positional arguments as properties. 1436 | 1437 | * **Returns:** *function* 1438 | 1439 | * **Example:** 1440 | 1441 | ```js 1442 | function foo({ x, y }) { return x + y; } 1443 | 1444 | var f = FPO.unapply( {fn: foo, props:["x","y"]} ); 1445 | 1446 | f( 1, 2 ); // 3 1447 | ``` 1448 | 1449 | * **Aliases:** `FPO.gather(..)` 1450 | 1451 | * **See Also:** [`FPO.apply(..)`](#fpoapply) 1452 | 1453 | ---- 1454 | 1455 | ### `FPO.unary(..)` 1456 | 1457 | ([back to top](#core-api)) 1458 | 1459 | Wraps a function to restrict its inputs to only one named argument as specified. 1460 | 1461 | * **Arguments:** 1462 | - `fn`: function to wrap 1463 | - `prop`: property name to allow as named argument 1464 | 1465 | * **Returns:** *function* 1466 | 1467 | * **Example:** 1468 | 1469 | ```js 1470 | functino foo(argsObj) { return argsObj; } 1471 | 1472 | var f = FPO.unary( {fn: foo, prop: "y"} ); 1473 | 1474 | f( {x: 1, y: 2, z: 3} ); 1475 | // { y:2 } 1476 | ``` 1477 | 1478 | * **See Also:** [`FPO.nAry(..)`](#fponary), [`FPO.binary(..)`](#fpobinary) 1479 | 1480 | ---- 1481 | 1482 | ### `FPO.uncurry(..)` 1483 | 1484 | ([back to top](#core-api)) 1485 | 1486 | Wraps a (strictly) curried function in a new function that accepts all the arguments at once, and provides them one at a time to the underlying curried function. 1487 | 1488 | * **Arguments:** 1489 | - `fn`: function to uncurry 1490 | 1491 | * **Returns:** *function* 1492 | 1493 | * **Example:** 1494 | 1495 | ```js 1496 | function foo({ x,y,z }) { return x + y + z; } 1497 | 1498 | var f = FPO.curry( {fn: foo, n: 3} ); 1499 | 1500 | var p = FPO.uncurry( {fn: f} ); 1501 | 1502 | p( {x: 1, y: 2, z: 3} ); // 6 1503 | ``` 1504 | 1505 | * **See Also:** [`FPO.curryMultiple(..)`](#fpocurrymultiple), [`FPO.partial(..)`](#fpopartial), [`FPO.uncurry(..)`](#fpouncurry) 1506 | 1507 | ---- 1508 | 1509 | ### `FPO.zip(..)` 1510 | 1511 | ([back to top](#core-api)) 1512 | 1513 | Produces a new array where each element is an array containing the value from that position in each of the respective input arrays. Only processes up to the shorter of the two arrays. 1514 | 1515 | * **Arguments:** 1516 | - `arr1`: the first array 1517 | - `arr2`: the second array 1518 | 1519 | * **Returns:** *array* 1520 | 1521 | * **Example:** 1522 | 1523 | ```js 1524 | var nums1 = [1,3,5,7]; 1525 | var nums2 = [2,4,6]; 1526 | 1527 | FPO.zip( {arr1: nums1, arr2: nums2} ); 1528 | // [[1,2],[3,4],[5,6]] 1529 | ``` 1530 | 1531 | * **See Also:** [`FPO.flatten(..)`](#fpoflatten) 1532 | -------------------------------------------------------------------------------- /docs/std-API.md: -------------------------------------------------------------------------------- 1 | # Standard API 2 | 3 | These are the methods on the `FPO.std.*` namespace. For the `FPO.*` methods, consult the [Core API documentation](core-API.md). 4 | 5 | * [`FPO.std.apply(..)`](#fpostdapply) (aliases: `FPO.std.spread(..)`) 6 | * [`FPO.std.binary(..)`](#fpostdbinary) 7 | * [`FPO.std.complement(..)`](#fpostdcomplement) 8 | * [`FPO.std.compose(..)`](#fpostdcompose) (aliases: `FPO.std.flowRight(..)`) 9 | * [`FPO.std.constant(..)`](#fpostdconstant) 10 | * [`FPO.std.curry(..)`](#fpostdcurry) 11 | * [`FPO.std.curryMultiple(..)`](#fpostdcurrymultiple) 12 | * [`FPO.std.filterIn(..)`](#fpostdfilterin) (aliases: `FPO.std.filter(..)`, `FPO.std.keep(..)`) 13 | * [`FPO.std.filterInObj(..)`](#fpostdfilterinobj) (aliases: `FPO.std.filterObj(..)`, `FPO.std.keepObj(..)`) 14 | * [`FPO.std.filterOut(..)`](#fpostdfilterout) (aliases: `FPO.std.reject(..)`) 15 | * [`FPO.std.filterOutObj(..)`](#fpostdfilteroutobj) (aliases: `FPO.std.rejectObj(..)`) 16 | * [`FPO.std.flatMap(..)`](#fpostdflatmap) (aliases: `FPO.std.chain(..)`) 17 | * [`FPO.std.flatMapObj(..)`](#fpostdflatmapobj) (aliases: `FPO.std.chainObj(..)`) 18 | * [`FPO.std.flatten(..)`](#fpostdflatten) 19 | * [`FPO.std.flip(..)`](#fpostdflip) 20 | * [`FPO.std.head(..)`](#fpostdhead) 21 | * [`FPO.std.identity(..)`](#fpostdidentity) 22 | * [`FPO.std.map(..)`](#fpostdmap) 23 | * [`FPO.std.mapObj(..)`](#fpostdmapobj) 24 | * [`FPO.std.memoize(..)`](#fpostdmemoize) 25 | * [`FPO.std.nAry(..)`](#fpostdnary) 26 | * [`FPO.std.partial(..)`](#fpostdpartial) 27 | * [`FPO.std.partialRight(..)`](#fpostdpartialright) 28 | * [`FPO.std.pick(..)`](#fpostdpick) 29 | * [`FPO.std.pickAll(..)`](#fpostdpickall) 30 | * [`FPO.std.pipe(..)`](#fpostdpipe) (aliases: `FPO.std.flow(..)`, `FPO.std.sequence(..)`) 31 | * [`FPO.std.prop(..)`](#fpostdprop) 32 | * [`FPO.std.reassoc(..)`](#fpostdreassoc) 33 | * [`FPO.std.reduce(..)`](#fpostdreduce) (aliases: `FPO.std.fold(..)`, `FPO.std.foldL(..)`) 34 | * [`FPO.std.reduceObj(..)`](#fpostdreduceobj) (aliases: `FPO.std.foldObj(..)`) 35 | * [`FPO.std.reduceRight(..)`](#fpostdreduceright) (aliases: `FPO.std.foldR(..)`) 36 | * [`FPO.std.remap(..)`](#fpostdremap) 37 | * [`FPO.std.reverseArgs(..)`](#fpostdreverseargs) 38 | * [`FPO.std.setProp(..)`](#fpostdsetprop) (aliases: `FPO.std.assoc(..)`) 39 | * [`FPO.std.tail(..)`](#fpostdtail) 40 | * [`FPO.std.take(..)`](#fpostdtake) 41 | * [`FPO.std.trampoline(..)`](#fpostdtrampoline) 42 | * **Transducers**: 43 | - [`FPO.std.transducers.array(..)`](#fpostdtransducersarray) 44 | - [`FPO.std.transducers.booleanAnd(..)`](#fpostdtransducersbooleanand) (aliases: `FPO.std.transducers.boolean(..)`) 45 | - [`FPO.std.transducers.booleanOr(..)`](#fpostdtransducersbooleanor) 46 | - [`FPO.std.transducers.default(..)`](#fpostdtransducersdefault) 47 | - [`FPO.std.transducers.filter(..)`](#fpostdtransducersfilter) 48 | - [`FPO.std.transducers.into(..)`](#fpostdtransducersinto) 49 | - [`FPO.std.transducers.map(..)`](#fpostdtransducersmap) 50 | - [`FPO.std.transducers.number(..)`](#fpostdtransducersnumber) 51 | - [`FPO.std.transducers.string(..)`](#fpostdtransducersstring) 52 | - [`FPO.std.transducers.transduce(..)`](#fpostdtransducerstransduce) 53 | * [`FPO.std.unapply(..)`](#fpostdunapply) (aliases: `FPO.std.gather(..)`) 54 | * [`FPO.std.unary(..)`](#fpostdunary) 55 | * [`FPO.std.uncurry(..)`](#fpostduncurry) 56 | * [`FPO.std.zip(..)`](#fpostdzip) 57 | 58 | ---- 59 | 60 | ### `FPO.std.apply(..)` 61 | 62 | ([back to top](#standard-api)) 63 | 64 | Wraps a function to spread out the elements from an array argument as individual positional arguments. 65 | 66 | * **Arguments:** 67 | - `fn`: function to wrap 68 | 69 | * **Returns:** *function* 70 | 71 | * **Example:** 72 | 73 | ```js 74 | function foo(x,y = 2) { return x + y; } 75 | function bar([a,b],c) { return a + b + c; } 76 | 77 | var f = FPO.std.apply( foo ); 78 | var p = FPO.std.apply( bar ); 79 | 80 | f( [1,1] ); // 2 81 | f( [3] ); // 5 82 | p( [[1,2],3] ); // 6 83 | ``` 84 | 85 | * **Aliases:** `FPO.std.spread(..)` 86 | 87 | * **See Also:** [`FPO.std.unapply(..)`](#fpostdunapply) 88 | 89 | ---- 90 | 91 | ### `FPO.std.binary(..)` 92 | 93 | ([back to top](#standard-api)) 94 | 95 | Wraps a function to restrict its inputs to only arguments. 96 | 97 | * **Arguments:** 98 | - `fn`: function to wrap 99 | 100 | * **Returns:** *function* 101 | 102 | * **Example:** 103 | 104 | ```js 105 | function foo(...args) { return args; } 106 | 107 | var f = FPO.std.binary( foo ); 108 | 109 | f( 1, 2, 3 ); // [1,2] 110 | ``` 111 | 112 | * **See Also:** [`FPO.std.nAry(..)`](#fpostdnary), [`FPO.std.unary(..)`](#fpostdunary) 113 | 114 | ---- 115 | 116 | ### `FPO.std.complement(..)` 117 | 118 | ([back to top](#standard-api)) 119 | 120 | Wraps a predicate function -- a function that produces `true` / `false` -- to negate its result. 121 | 122 | * **Arguments:** 123 | - `fn`: function to wrap 124 | 125 | * **Returns:** *function* 126 | 127 | * **Example:** 128 | 129 | ```js 130 | var f = FPO.std.complement( () => false ); 131 | 132 | f(); // true 133 | ``` 134 | 135 | ---- 136 | 137 | ### `FPO.std.compose(..)` 138 | 139 | ([back to top](#standard-api)) 140 | 141 | Produces a new function that's the composition of a list of functions. Functions are composed right-to-left (unlike [`FPO.std.pipe(..)`](#fpostdpipe)) from the array. 142 | 143 | * **Arguments:** 144 | - `fns`: array of functions 145 | 146 | * **Returns:** *function* 147 | 148 | * **Example:** 149 | 150 | ```js 151 | var f = FPO.std.compose( [ 152 | v => v + "3", 153 | v => v + "2", 154 | v => v + "1" 155 | ] ); 156 | 157 | f( "0" ); // "0123" 158 | ``` 159 | 160 | * **Aliases:** `FPO.std.flowRight(..)` 161 | 162 | * **See Also:** [`FPO.std.pipe(..)`](#fpostdpipe) 163 | 164 | ---- 165 | 166 | ### `FPO.std.constant(..)` 167 | 168 | ([back to top](#standard-api)) 169 | 170 | Wraps a value in a function that returns the value. 171 | 172 | * **Arguments:** 173 | - `v`: value to wrap 174 | 175 | * **Returns:** *function* 176 | 177 | * **Example:** 178 | 179 | ```js 180 | var f = FPO.std.constant( 42 ); 181 | 182 | f(); // 42 183 | ``` 184 | 185 | * **See Also:** [`FPO.std.identity(..)`](#fpostdidentity) 186 | 187 | ---- 188 | 189 | ### `FPO.std.curry(..)` 190 | 191 | ([back to top](#standard-api)) 192 | 193 | Curries a function so that you can pass one argument at a time, each time getting back another function to receive the next argument. Once all arguments are passed, the underlying function is called with the arguments. 194 | 195 | * **Arguments:** 196 | - `fn`: function to curry 197 | - `n`: (optional) number of arguments to curry for; if omitted, tries to detect the arity (`fn.length`) to use. 198 | 199 | * **Returns:** *function* 200 | 201 | * **Example:** 202 | 203 | ```js 204 | function foo(x,y,z) { return x + y + z; } 205 | 206 | var f = FPO.std.curry( foo, 3 ); 207 | var p = FPO.std.curry( foo ); 208 | 209 | f()( "a" )( "b" )()( "c" ); 210 | // "abc" 211 | 212 | p( "a" )( "b" )( "c" ); 213 | // "abc" 214 | ``` 215 | 216 | * **See Also:** [`FPO.std.curryMultiple(..)`](#fpostdcurrymultiple), [`FPO.std.partial(..)`](#fpostdpartial), [`FPO.std.uncurry(..)`](#fpostduncurry) 217 | 218 | ---- 219 | 220 | ### `FPO.std.curryMultiple(..)` 221 | 222 | ([back to top](#standard-api)) 223 | 224 | Just like [`FPO.std.curry(..)`](#fpostdcurry), except each curried function allows multiple arguments instead of just one. 225 | 226 | * **Arguments:** 227 | - `fn`: function to curry 228 | - `n`: (optional) number of arguments to curry for; if omitted, tries to detect the arity (`fn.length`) to use. 229 | 230 | * **Returns:** *function* 231 | 232 | * **Example:** 233 | 234 | ```js 235 | function foo(x,y,z) { return x + y + z; } 236 | 237 | var f = FPO.std.curryMultiple( foo, 3 ); 238 | var p = FPO.std.curryMultiple( foo ); 239 | 240 | f()( "a" )()( "b", "c" ); 241 | // "abc" 242 | 243 | p( "a" )( "b", "c" ); 244 | // "abc" 245 | ``` 246 | 247 | * **See Also:** [`FPO.std.curry(..)`](#fpostdcurry), [`FPO.std.partial(..)`](#fpostdpartial), [`FPO.std.uncurry(..)`](#fpostduncurry) 248 | 249 | ---- 250 | 251 | ### `FPO.std.filterIn(..)` 252 | 253 | ([back to top](#standard-api)) 254 | 255 | Commonly known as `filter(..)`, produces a new list by calling a predicate function with each value in the original list. For each value, if the predicate function returns true (or truthy), the value is included in (aka, filtered into) the new list. Otherwise, the value is omitted. 256 | 257 | * **Arguments:** 258 | - `fn`: predicate function; called with `v` (value), `i` (index), and `arr` (array) arguments 259 | - `arr`: array to filter against 260 | 261 | * **Returns:** *array* 262 | 263 | * **Example:** 264 | 265 | ```js 266 | function isOdd(v) { return v % 2 == 1; } 267 | 268 | var nums = [1,2,3,4,5]; 269 | 270 | FPO.std.filterIn( isOdd, nums ); 271 | // [1,3,5] 272 | ``` 273 | 274 | * **Aliases:** `FPO.std.filter(..)`, `FPO.std.keep(..)` 275 | 276 | * **See Also:** [`FPO.std.filterInObj(..)`](#fpostdfilterinobj), [`FPO.std.filterOut(..)`](#fpostdfilterout) 277 | 278 | ---- 279 | 280 | ### `FPO.std.filterInObj(..)` 281 | 282 | ([back to top](#standard-api)) 283 | 284 | Produces a new object by calling a predicate function with each property value in the original object. For each value, if the predicate function returns true (or truthy), the value is included in (aka, filtered into) the new object at the same property name. Otherwise, the value is omitted. 285 | 286 | * **Arguments:** 287 | - `fn`: predicate function; called with `v` (value), `i` (property name), and `o` (object) arguments 288 | - `o`: object to filter against 289 | 290 | * **Returns:** *object* 291 | 292 | * **Example:** 293 | 294 | ```js 295 | function isOdd(v) { return v % 2 == 1; } 296 | 297 | var nums = {a: 1, b: 2, c: 3, d: 4, e: 5}; 298 | 299 | FPO.std.filterInObj( isOdd, nums ); 300 | // {a: 1, c: 3, e: 5} 301 | ``` 302 | 303 | * **Aliases:** `FPO.std.filterObj(..)`, `FPO.std.keepObj(..)` 304 | 305 | * **See Also:** [`FPO.std.filterIn(..)`](#fpostdfilterin), [`FPO.std.filterOutObj(..)`](#fpostdfilteroutobj) 306 | 307 | ---- 308 | 309 | ### `FPO.std.filterOut(..)` 310 | 311 | ([back to top](#standard-api)) 312 | 313 | The inverse of [`FPO.std.filterIn(..)`](#fpostdfilterin), produces a new list by calling a predicate function with each value in the original list. For each value, if the predicate function returns true (or truthy), the value is omitted from (aka, filtered out of) the new list. Otherwise, the value is included. 314 | 315 | * **Arguments:** 316 | - `fn`: predicate function; called with `v` (value), `i` (index), and `arr` (array) arguments 317 | - `arr`: array to filter against 318 | 319 | * **Returns:** *array* 320 | 321 | * **Example:** 322 | 323 | ```js 324 | function isOdd(v) { return v % 2 == 1; } 325 | 326 | var nums = [1,2,3,4,5]; 327 | 328 | FPO.std.filterOut( isOdd, nums ); 329 | // [2,4] 330 | ``` 331 | 332 | * **Aliases:** `FPO.std.reject(..)` 333 | 334 | * **See Also:** [`FPO.std.filterOutObj(..)`](#fpostdfilteroutobj), [`FPO.std.filterIn(..)`](#fpostdfilterin) 335 | 336 | ---- 337 | 338 | ### `FPO.std.filterOutObj(..)` 339 | 340 | ([back to top](#standard-api)) 341 | 342 | The inverse of [`FPO.std.filterInObj(..)`](#fpostdfilterinobj), produces a new object by calling a predicate function with each property value in the original object. For each value, if the predicate function returns true (or truthy), the value is omitted from (aka, filtered out of) the new object. Otherwise, the value is included at the same property name. 343 | 344 | * **Arguments:** 345 | - `fn`: predicate function; called with `v` (value), `i` (property name), and `o` (object) arguments 346 | - `o`: object to filter against 347 | 348 | * **Returns:** *object* 349 | 350 | * **Example:** 351 | 352 | ```js 353 | function isOdd(v) { return v % 2 == 1; } 354 | 355 | var nums = {a: 1, b: 2, c: 3, d: 4, e: 5}; 356 | 357 | FPO.std.filterOutObj( isOdd, nums ); 358 | // {b: 2, d: 4} 359 | ``` 360 | 361 | * **Aliases:** `FPO.std.rejectObj(..)` 362 | 363 | * **See Also:** [`FPO.std.filterOut(..)`](#fpostdfilterout), [`FPO.std.filterInObj(..)`](#fpostdfilterinobj) 364 | 365 | ---- 366 | 367 | ### `FPO.std.flatMap(..)` 368 | 369 | ([back to top](#standard-api)) 370 | 371 | Similar to [`FPO.std.map(..)`](#fpostdmap), produces a new list by calling a mapper function with each value in the original list. If the mapper function returns an array, this array is flattened (one level) into the overall array. 372 | 373 | * **Arguments:** 374 | - `fn`: mapper function; called with `v` (value), `i` (index), and `arr` (array) arguments 375 | - `arr`: array to flat-map against 376 | 377 | * **Returns:** *array* 378 | 379 | * **Example:** 380 | 381 | ```js 382 | function splitChars(v) { return [...v]; } 383 | 384 | var words = ["hello","world"]; 385 | 386 | splitChars( words[0] ); 387 | // ["h","e","l","l","o"] 388 | 389 | FPO.std.map( splitChars, words ); 390 | // [["h","e","l","l","o"],["w","o","r","l","d"]] 391 | 392 | FPO.std.flatMap( splitChars, words ); 393 | // ["h","e","l","l","o","w","o","r","l","d"] 394 | ``` 395 | 396 | * **Aliases:** `FPO.std.chain(..)` 397 | 398 | * **See Also:** [`FPO.std.flatMapObj(..)`](#fpostdflatmapobj), [`FPO.std.map(..)`](#fpostdmap), [`FPO.std.flatten(..)`](#fpostdflatten) 399 | 400 | ---- 401 | 402 | ### `FPO.std.flatMapObj(..)` 403 | 404 | ([back to top](#standard-api)) 405 | 406 | Similar to [`FPO.std.mapObj(..)`](#fpostdmapobj), produces a new object by calling a mapper function with each property value in the original object. If the mapper function returns an object, this object is flattened (one level) into the overall object, by copying its properties. 407 | 408 | * **Arguments:** 409 | - `fn`: mapper function; called with `v` (value), `i` (property name), and `o` (object) arguments 410 | - `o`: object to flat-map against 411 | 412 | * **Returns:** *object* 413 | 414 | * **Example:** 415 | 416 | ```js 417 | function splitEvensInHalf(v,i) { 418 | if (v % 2 == 0) { 419 | return { [i]: v/2, [i+"_2"]: v/2 }; 420 | } 421 | return v; 422 | } 423 | 424 | var nums = {a: 1, b: 2, c: 3, d: 4}; 425 | 426 | splitEvensInHalf( 3, "c" ); 427 | // 3 428 | 429 | splitEvensInHalf( 4, "d" ); 430 | // {d: 2, d_2: 2} 431 | 432 | FPO.std.mapObj( splitEvensInHalf, nums ); 433 | // {a: 1, b: {b: 1, b_2: 1}, c: 3, d: {d: 2, d_2: 2}} 434 | 435 | FPO.std.flatMapObj( splitEvensInHalf, nums ); 436 | // {a: 1, b: 1, b_2: 1, c: 3, d: 2, d_2: 2}; 437 | ``` 438 | 439 | * **Aliases:** `FPO.std.chainObj(..)` 440 | 441 | * **See Also:** [`FPO.std.flatMap(..)`](#fpostdflatmap), [`FPO.std.mapObj(..)`](#fpostdmapobj) 442 | 443 | ---- 444 | 445 | ### `FPO.std.flatten(..)` 446 | 447 | ([back to top](#standard-api)) 448 | 449 | Flattens an array of nested arrays. Optionally, specify how many levels of nesting to flatten out. 450 | 451 | * **Arguments:** 452 | - `arr`: array to flat-map against 453 | - `n`: (optional) the number of levels of nesting to flatten out; if omitted, defaults to Infinity (to flatten any nested depth). 454 | 455 | * **Returns:** *array* 456 | 457 | * **Example:** 458 | 459 | ```js 460 | var nums = [1,2,[3,4],[5,[6,7]]]; 461 | 462 | FPO.std.flatten( nums ); 463 | // [1,2,3,4,5,6,7] 464 | 465 | FPO.std.flatten( nums, 1 ); 466 | // [1,2,3,4,5,[6,7]] 467 | 468 | FPO.std.flatten( nums, 2 ); 469 | // [1,2,3,4,5,6,7] 470 | ``` 471 | 472 | * **See Also:** [`FPO.std.flatMap(..)`](#fpostdflatmap) 473 | 474 | ---- 475 | 476 | ### `FPO.std.flip(..)` 477 | 478 | ([back to top](#standard-api)) 479 | 480 | Wraps a function to flip (transpose) the first two arguments it receives. 481 | 482 | * **Arguments:** 483 | - `fn`: function to wrap 484 | 485 | * **Returns:** *function* 486 | 487 | * **Example:** 488 | 489 | ```js 490 | function foo(...args) { return args; } 491 | 492 | var f = FPO.std.flip( foo ); 493 | 494 | f( 1, 2, 3, 4 ); 495 | // [2,1,3,4] 496 | ``` 497 | 498 | * **See Also:** [`FPO.std.reverseArgs(..)`](#fpostdreverseargs) 499 | 500 | ---- 501 | 502 | ### `FPO.std.head(..)` 503 | 504 | ([back to top](#standard-api)) 505 | 506 | Returns the element as accessed at index 0 of the value. 507 | 508 | * **Arguments:** 509 | - `v`: array, string, object 510 | 511 | * **Returns:** *any* 512 | 513 | * **Example:** 514 | 515 | ```js 516 | var nums = [1,2,3,4]; 517 | 518 | FPO.std.head( nums ); 519 | // 1 520 | 521 | FPO.std.head( [] ); 522 | // undefined 523 | 524 | FPO.std.head( "abc" ); 525 | // "a" 526 | 527 | FPO.std.head( {0: 42} ); 528 | // 42 529 | ``` 530 | 531 | * **See Also:** [`FPO.tail(..)`](#fpotail) 532 | 533 | ---- 534 | 535 | ### `FPO.std.identity(..)` 536 | 537 | ([back to top](#standard-api)) 538 | 539 | Returns the value given to it. Useful as a default placeholder for certain opertaions (i.e., composition, reduction). 540 | 541 | * **Arguments:** 542 | - `v`: value to return 543 | 544 | * **Returns:** *-any-* 545 | 546 | * **Example:** 547 | 548 | ```js 549 | FPO.std.identity( 42 ); // 42 550 | ``` 551 | 552 | * **See Also:** [`FPO.std.constant(..)`](#fpostdconstant) 553 | 554 | ---- 555 | 556 | ### `FPO.std.map(..)` 557 | 558 | ([back to top](#standard-api)) 559 | 560 | Produces a new list by calling a mapper function with each value in the original list. The value the mapper function returns is inserted in the new list at that same position. The new list will always be the same length as the original list. 561 | 562 | * **Arguments:** 563 | - `fn`: mapper function; called with `v` (value), `i` (index), and `arr` (array) arguments 564 | - `arr`: array to map against 565 | 566 | * **Returns:** *array* 567 | 568 | * **Example:** 569 | 570 | ```js 571 | function double(v) { return v * 2; } 572 | 573 | var nums = [1,2,3,4,5]; 574 | 575 | FPO.std.map( double, nums ); 576 | // [2,4,6,8,10] 577 | ``` 578 | 579 | * **See Also:** [`FPO.std.mapObj(..)`](#fpostdmapobj), [`FPO.std.flatMap(..)`](#fpostdflatmap) 580 | 581 | ---- 582 | 583 | ### `FPO.std.mapObj(..)` 584 | 585 | ([back to top](#standard-api)) 586 | 587 | Produces a new object by calling a mapper function with each property value in the original object. The value the mapper function returns is inserted in the new object at that same property name. The new object will always have the same number of properties as the original object. 588 | 589 | * **Arguments:** 590 | - `fn`: mapper function; called with `v` (value), `i` (property name), and `o` (object) arguments 591 | - `o`: object to map against 592 | 593 | * **Returns:** *object* 594 | 595 | * **Example:** 596 | 597 | ```js 598 | function double(v) { return v * 2; } 599 | 600 | var nums = {a: 1, b: 2, c: 3, d: 4, e: 5}; 601 | 602 | FPO.mapObj(double, nums ); 603 | // {a: 2, b: 4, c: 6, d: 8, e: 10}; 604 | ``` 605 | 606 | * **See Also:** [`FPO.std.map(..)`](#fpostdmap) 607 | 608 | ---- 609 | 610 | ### `FPO.std.memoize(..)` 611 | 612 | ([back to top](#standard-api)) 613 | 614 | For performance optimization reasons, wraps a function such that it remembers each set of arguments passed to it, associated with that underlying return value. If the wrapped function is called subsequent times with the same set of arguments, the cached return value is returned instead of being recomputed. Each wrapped function instance has its own separate cache, even if wrapping the same original function multiple times. 615 | 616 | A set of arguments is "remembered" by being hashed to a string value to use as a cache key. This hashing is done internally with `JSON.stringify(..)`, which is fast and works with many common JS value types. However, this hashing is by no means bullet-proof for all types, and does not guarantee collision-free. **Use caution:** generally, you should only use primitives (number, string, boolean, null, and undefined) or simple objects (object, array) as arguments. If you use objects, always make sure to list properties in the same order to ensure proper hashing. 617 | 618 | By default, the function's arity (`fn.length`) will be detected as `n`. However, in JS certain techniques thwart this detection, such as the use of default parameters or parameter destructuring. Make sure to specify the correct `n` if detection is uncertain or unreliable. 619 | 620 | Unary functions (single argument; `n` of `1`) with a primitive argument are the fastest for memoization, so if possible, try to design functions that way. In these cases, specifying `n` as `1` will help ensure the best possible performance. 621 | 622 | **Warning:** Be aware that if `1` is initially specified (or detected) for `n`, additional arguments later passed to the wrapped function are **not considered** in the memoization hashing, though they will still be passed to the underlying function as-is. This may cause unexpected results (false-positives on cache hits); always make sure `n` matches the expected number of arguments. 623 | 624 | * **Arguments:** 625 | - `fn`: function to wrap 626 | - `n`: number of arguments to memoize; if omitted, tries to detect the arity (`fn.length`) to use. 627 | 628 | * **Returns:** *array* 629 | 630 | * **Example:** 631 | 632 | ```js 633 | function sum(x,y) { console.log( "sum called!" ); return x + y; } 634 | function mult({x, y}) { console.log( "mult called!" ); return x * y; } 635 | 636 | var A = FPO.std.memoize( sum ); 637 | var B = FPO.std.memoize( sum, 1 ); // be careful here! 638 | var C = FPO.std.memoize( mult, 1 ); // 1 refers to the single object, not its property count 639 | 640 | A( 2, 3 ); 641 | // sum called! 642 | // 5 643 | 644 | A( 2, 3 ); // no need to re-compute, value pulled from cache 645 | // 5 646 | 647 | B( 2, 3 ); // different instance, separate cache, re-computed 648 | // sum called! 649 | // 5 650 | 651 | B( 2, 100 ); // oops, memoization fail here! 652 | // 5 653 | 654 | C( {x: 3, y: 4} ); 655 | // mult called! 656 | // 12 657 | 658 | C( {x: 3, y: 4} ); 659 | // 12 660 | 661 | C( {y: 4, x: 3} ); // oops, cache hashing is different 662 | // mult called! 663 | // 12 664 | ``` 665 | 666 | ---- 667 | 668 | ### `FPO.std.nAry(..)` 669 | 670 | ([back to top](#standard-api)) 671 | 672 | Wraps a function to restrict its inputs to only the count specified. 673 | 674 | * **Arguments:** 675 | - `fn`: function to wrap 676 | - `n`: desired arity count; if omitted, defaults to 0, which produces a "nullary" function -- won't receive any arguments. 677 | 678 | * **Returns:** *function* 679 | 680 | * **Example:** 681 | 682 | ```js 683 | function foo(...args) { return args; } 684 | 685 | var f = FPO.std.nAry( foo, 3 ); 686 | 687 | f( 1, 2, 3, 4 ); // [1,2,3] 688 | ``` 689 | 690 | * **See Also:** [`FPO.std.binary(..)`](#fpostdbinary), [`FPO.std.unary(..)`](#fpostdunary) 691 | 692 | ---- 693 | 694 | ### `FPO.std.partial(..)` 695 | 696 | ([back to top](#standard-api)) 697 | 698 | Wraps a function with a new function that already has some of the arguments pre-specified, and is waiting for the rest of them on the next call. Unlike [`FPO.std.curry(..)`](#fpostdcurry), you must specify all the remaining arguments on the next call of the partially-applied function. 699 | 700 | Partial application happens from left-to-right. If you'd need to partially-apply from the right, use [`FPO.std.partialRight(..)`](#fpostdpartialright) 701 | 702 | * **Arguments:** 703 | - `fn`: function to partially-apply 704 | - `args`: array containing the arguments to apply now 705 | 706 | * **Returns:** *function* 707 | 708 | * **Example:** 709 | 710 | ```js 711 | function foo(x,y,z) { return x + y + z; } 712 | 713 | var f = FPO.std.partial( foo, ["a"] ); 714 | 715 | f( "b", "!" ); 716 | // "ab!" 717 | ``` 718 | 719 | * **See Also:** [`FPO.std.partialRight(..)`](#fpostdpartialright), [`FPO.std.curry(..)`](#fpostdcurry), [`FPO.std.curryMultiple(..)`](#fpostdcurrymultiple) 720 | 721 | ---- 722 | 723 | ### `FPO.std.partialRight(..)` 724 | 725 | ([back to top](#standard-api)) 726 | 727 | Wraps a function with a new function that already has some of the arguments pre-specified, and is waiting for the rest of them on the next call. Unlike [`FPO.std.curry(..)`](#fpostdcurry), you must specify all the remaining arguments on the next call of the partially-applied function. 728 | 729 | Right-partial application happens from right-to-left. If you'd like to partially-apply from the left, use [`FPO.std.partial(..)`](#fpostdpartial) 730 | 731 | * **Arguments:** 732 | - `fn`: function to partially-apply 733 | - `args`: array containing the arguments to apply now 734 | 735 | * **Returns:** *function* 736 | 737 | * **Example:** 738 | 739 | ```js 740 | function foo(x,y,z) { return x + y + z; } 741 | 742 | var f = FPO.std.partialRight( foo, ["b","!"] ); 743 | 744 | f( "a" ); 745 | // "ab!" 746 | ``` 747 | 748 | * **See Also:** [`FPO.std.partial(..)`](#fpostdpartial), [`FPO.std.curry(..)`](#fpostdcurry), [`FPO.std.curryMultiple(..)`](#fpostdcurrymultiple) 749 | 750 | ---- 751 | 752 | ### `FPO.std.pick(..)` 753 | 754 | ([back to top](#standard-api)) 755 | 756 | Returns a new object with only the specified properties from the original object. Includes only properties from the original object. 757 | 758 | * **Arguments:** 759 | - `props`: array of property names to pick from the object; if a property does not exist on the original object, it **is not** added to the new object, unlike [`FPO.std.pickAll(..)`](#fpostdpickall). 760 | - `v`: object to pick properties from 761 | 762 | * **Returns:** *object* 763 | 764 | * **Example:** 765 | 766 | ```js 767 | var obj = { x: 1, y: 2, z: 3 }; 768 | 769 | FPO.std.pick( ["x","y","w"], obj ); 770 | // { x:1, y:2 } 771 | ``` 772 | 773 | * **See Also:** [`FPO.std.pickAll(..)`](#fpostdpickall), [`FPO.std.prop(..)`](#fpostdprop) 774 | 775 | ---- 776 | 777 | ### `FPO.std.pickAll(..)` 778 | 779 | ([back to top](#standard-api)) 780 | 781 | Returns a new object with only the specified properties from the original object. Includes all specified properties. 782 | 783 | * **Arguments:** 784 | - `props`: array of property names to pick from the object; even if a property does not exist on the original object, it **is** still added to the new object with an `undefined` value, unlike [`FPO.std.pick(..)`](#fpostdpick). 785 | - `v`: object to pick properties from 786 | 787 | * **Returns:** *object* 788 | 789 | * **Example:** 790 | 791 | ```js 792 | var obj = { x: 1, y: 2, z: 3 }; 793 | 794 | FPO.std.pickAll( ["x","y","w"], obj ); 795 | // { x:1, y:2, w:undefined } 796 | ``` 797 | 798 | * **See Also:** [`FPO.std.pick(..)`](#fpostdpick) 799 | 800 | ---- 801 | 802 | ### `FPO.std.pipe(..)` 803 | 804 | ([back to top](#standard-api)) 805 | 806 | Produces a new function that's the composition of a list of functions. Functions are composed left-to-right (unlike [`FPO.std.compose(..)`](#fpostdcompose)) from the array. 807 | 808 | * **Arguments:** 809 | - `fns`: array of functions 810 | 811 | * **Returns:** *function* 812 | 813 | * **Example:** 814 | 815 | ```js 816 | var f = FPO.std.pipe( [ 817 | v => v + "3", 818 | v => v + "2", 819 | v => v + "1" 820 | ] ); 821 | 822 | f( "4" ); // "4321" 823 | ``` 824 | 825 | * **Aliases:** `FPO.std.flow(..)`, `FPO.std.sequence(..)` 826 | 827 | * **See Also:** [`FPO.std.compose(..)`](#fpostdcompose) 828 | 829 | ---- 830 | 831 | ### `FPO.std.prop(..)` 832 | 833 | ([back to top](#standard-api)) 834 | 835 | Extracts a property's value from an object. 836 | 837 | * **Arguments:** 838 | - `prop`: property name to pull from the object 839 | - `v`: object to pull the property value from 840 | 841 | * **Returns:** *-any-* 842 | 843 | * **Example:** 844 | 845 | ```js 846 | var obj = { x: 1, y: 2, z: 3 }; 847 | 848 | FPO.std.prop( "y", obj ); // 2 849 | ``` 850 | 851 | * **See Also:** [`FPO.std.pick(..)`](#fpostdpick), [`FPO.std.setProp(..)`](#fpostdsetprop) 852 | 853 | ---- 854 | 855 | ### `FPO.std.reassoc(..)` 856 | 857 | ([back to top](#standard-api)) 858 | 859 | Like a mixture between [`FPO.std.pick(..)`](#fpostdpick) and [`FPO.std.setProp(..)`](#fpostdsetprop), creates a new object that has properties remapped from original names to new names. Any properties present on the original object that aren't remapped are copied with the same name. 860 | 861 | * **Arguments:** 862 | - `props`: object whose key/value pairs are `sourceProp: targetProp` remappings 863 | - `v`: object to remap properties from 864 | 865 | * **Returns:** *object* 866 | 867 | * **Example:** 868 | 869 | ```js 870 | var obj = { x: 1, y: 2, z: 3 }; 871 | 872 | FPO.std.reassoc( {x: "A", z: "__z__"}, obj ); 873 | // { A: 1, __z__: 3, y: 2} 874 | ``` 875 | 876 | * **See Also:** [`FPO.std.pick(..)`](#fpostdpick), [`FPO.std.setProp(..)`](#fpostdsetprop) 877 | 878 | ---- 879 | 880 | ### `FPO.std.reduce(..)` 881 | 882 | ([back to top](#standard-api)) 883 | 884 | Processes a list from left-to-right (unlike [`FPO.std.reduceRight(..)`](#fpostdreduceright)), successively combining (aka "reducing", "folding") two values into one, until the entire list has been reduced to a single value. An initial value for the reduction can optionally be provided. 885 | 886 | * **Arguments:** 887 | - `fn`: reducer function; called with `acc` (accumulator), `v` (value), `i` (index), and `arr` (array) arguments 888 | - `v`: (optional) initial value to use for the reduction; if provided, the first reduction will pass to the reducer the initial value as the `acc` and the first value from the array as `v`. Otherwise, the first reduction has the first value of the array as `acc` and the second value of the array as `v`. 889 | - `arr`: array to reduce 890 | 891 | * **Returns:** *-any-* 892 | 893 | * **Example:** 894 | 895 | ```js 896 | function strConcat(acc,v) { return acc + v; } 897 | 898 | var vowels = ["a","e","i","o","u","y"]; 899 | 900 | FPO.std.reduce( strConcat, undefined, vowels ); 901 | // "aeiouy" 902 | 903 | FPO.std.reduce( strConcat, "vowels: ", vowels ); 904 | // "vowels: aeiouy" 905 | ``` 906 | 907 | * **Aliases:** `FPO.std.fold(..)`, `FPO.std.foldL(..)` 908 | 909 | * **See Also:** [`FPO.std.reduceObj(..)`](#fpostdreduceobj), [`FPO.std.reduceRight(..)`](#fpostdreduceright) 910 | 911 | ---- 912 | 913 | ### `FPO.std.reduceObj(..)` 914 | 915 | ([back to top](#standard-api)) 916 | 917 | Processes an object's properties (in enumeration order), successively combining (aka "reducing", "folding") two values into one, until all the object's properties have been reduced to a single value. An initial value for the reduction can optionally be provided. 918 | 919 | **Note:** Enumeration order of properties is not strictly guaranteed cross-environment. However, it's generally reliable as the order that properties were listed/added to the object in its definition. 920 | 921 | * **Arguments:** 922 | - `fn`: reducer function; called with `acc` (accumulator), `v` (value), `i` (property name), and `o` (object) arguments 923 | - `o`: object to reduce 924 | - `v`: (optional) initial value to use for the reduction; if provided, the first reduction will pass to the reducer the initial value as the `acc` and the first property value (in enumeration order) from the object as `v`. Otherwise, the first reduction has the first property value (in enumeration order) of the object as `acc` and the second property value (in enumeration order) of the object as `v`. 925 | 926 | * **Returns:** *-any-* 927 | 928 | * **Example:** 929 | 930 | ```js 931 | function strConcat(acc,v) { return acc + v; } 932 | 933 | var vowels = {a: "a", b: "e", c: "i", d: "o", e: "u", f: "y"}; 934 | 935 | FPO.std.reduceObj( strConcat, undefined, vowels ); 936 | // "aeiouy" 937 | 938 | FPO.std.reduceObj( strConcat, "vowels: ", vowels ); 939 | // "vowels: aeiouy" 940 | ``` 941 | 942 | * **Aliases:** `FPO.std.foldObj(..)` 943 | 944 | * **See Also:** [`FPO.std.reduce(..)`](#fpostdreduce) 945 | 946 | ---- 947 | 948 | ### `FPO.std.reduceRight(..)` 949 | 950 | ([back to top](#standard-api)) 951 | 952 | Processes a list from right-to-left (unlike [`FPO.std.reduce(..)`](#fpostdreduce)), successively combining (aka "reducing", "folding") two values into one, until the entire list has been reduced to a single value. 953 | 954 | An initial value for the reduction can optionally be provided. If the array is empty, the initial value is returned (or `undefined` if it was omitted). 955 | 956 | * **Arguments:** 957 | - `fn`: reducer function; called with `acc` (accumulator), `v` (value), `i` (index), and `arr` (array) arguments 958 | - `arr`: array to reduce 959 | - `v`: (optional) initial value to use for the reduction; if provided, the first reduction will pass to the reducer the initial value as the `acc` and the first value from the array as `v`. Otherwise, the first reduction has the first value of the array as `acc` and the second value of the array as `v`. 960 | 961 | * **Returns:** *-any-* 962 | 963 | * **Example:** 964 | 965 | ```js 966 | function strConcat(acc,v) { return acc + v; } 967 | 968 | var vowels = ["a","e","i","o","u","y"]; 969 | 970 | FPO.std.reduceRight( strConcat, undefined, vowels ); 971 | // "yuoiea" 972 | 973 | FPO.std.reduceRight( strConcat, "vowels: ", vowels ); 974 | // "vowels: yuoiea" 975 | ``` 976 | 977 | * **Aliases:** `FPO.std.foldR(..)` 978 | 979 | * **See Also:** [`FPO.std.reduce(..)`](#fpostdreduce) 980 | 981 | ---- 982 | 983 | ### `FPO.std.remap(..)` 984 | 985 | ([back to top](#standard-api)) 986 | 987 | Remaps the expected named arguments of a function. This is useful to adapt a function to be used if the arguments passed in will be different than what the function expects. 988 | 989 | A common usecase will be to adapt a function so it's suitable for use as a mapper/predicate/reducer function, or for composition. 990 | 991 | * **Arguments:** 992 | - `fn`: function to remap 993 | - `args`: object whose key/value pairs represent the `origArgName: newArgName` mappings 994 | 995 | * **Returns:** *function* 996 | 997 | * **Example:** 998 | 999 | ```js 1000 | function double({ x }) { return x * 2; } 1001 | 1002 | var f = FPO.std.remap( double, {x: "v"} ); 1003 | 1004 | f( {v: 3} ); 1005 | // 6 1006 | ``` 1007 | 1008 | * **See Also:** [`FPO.std.reassoc(..)`](#fpostdreassoc) 1009 | 1010 | ---- 1011 | 1012 | ### `FPO.std.reverseArgs(..)` 1013 | 1014 | ([back to top](#standard-api)) 1015 | 1016 | Wraps a function to reverse the order of all received arguments. 1017 | 1018 | * **Arguments:** 1019 | - `fn`: function to wrap 1020 | 1021 | * **Returns:** *function* 1022 | 1023 | * **Example:** 1024 | 1025 | ```js 1026 | function foo(...args) { return args; } 1027 | 1028 | var f = FPO.std.reverseArgs( foo ); 1029 | 1030 | f( 1, 2, 3, 4 ); 1031 | // [4,3,2,1] 1032 | ``` 1033 | 1034 | * **See Also:** [`FPO.std.flip(..)`](#fpostdflip) 1035 | 1036 | ---- 1037 | 1038 | ### `FPO.std.setProp(..)` 1039 | 1040 | ([back to top](#standard-api)) 1041 | 1042 | Creates a shallow clone of an object, assigning the specified property value to the new object. 1043 | 1044 | * **Arguments:** 1045 | - `prop`: property name where to set the value on the new object 1046 | - `o`: (optional) object to clone; if omitted, defaults to a new empty object 1047 | - `v`: value 1048 | 1049 | * **Returns:** *object* 1050 | 1051 | * **Example:** 1052 | 1053 | ```js 1054 | var obj = { x: 1, y: 2, z: 3 }; 1055 | 1056 | FPO.std.setProp( "w", obj, 4 ); 1057 | // { x:1, y:2, z:3, w:4 } 1058 | 1059 | obj; 1060 | // { x:1, y:2, z:3 } 1061 | ``` 1062 | 1063 | * **Aliases:** `FPO.std.assoc(..)` 1064 | 1065 | * **See Also:** [`FPO.prop(..)`](#fpoprop) 1066 | 1067 | ---- 1068 | 1069 | ### `FPO.std.tail(..)` 1070 | 1071 | ([back to top](#standard-api)) 1072 | 1073 | Returns everything else in the value except the element as accessed at index 0; basically the inverse of [`FPO.std.head(..)`](#fpostdhead). 1074 | 1075 | * **Arguments:** 1076 | - `v`: array, string, object 1077 | 1078 | * **Returns:** *any* 1079 | 1080 | * **Example:** 1081 | 1082 | ```js 1083 | var nums = [1,2,3,4]; 1084 | 1085 | FPO.std.tail( nums ); 1086 | // [2,3,4] 1087 | 1088 | FPO.std.tail( [] ); 1089 | // [] 1090 | 1091 | FPO.std.tail( "abc" ); 1092 | // "bc" 1093 | 1094 | FPO.std.tail( {0: 42, 1: 10} ); 1095 | // {1: 10} 1096 | ``` 1097 | 1098 | * **See Also:** [`FPO.std.head(..)`](#fpostdhead) 1099 | 1100 | ---- 1101 | 1102 | ### `FPO.std.take(..)` 1103 | 1104 | ([back to top](#standard-api)) 1105 | 1106 | Returns the specified number of elements from the value, starting from the beginning. 1107 | 1108 | * **Arguments:** 1109 | - `v`: array / string 1110 | - `n`: number of elements to take from the beginning of the value; if omitted, defaults to `1`. 1111 | 1112 | * **Returns:** array / string 1113 | 1114 | * **Example:** 1115 | 1116 | ```js 1117 | var nums = [1,2,3,4]; 1118 | 1119 | FPO.std.take( nums, 2 ); 1120 | // [1,2] 1121 | 1122 | FPO.std.take( nums ); 1123 | // [1] 1124 | 1125 | FPO.std.take( "abc", 2 ); 1126 | // "ab" 1127 | 1128 | FPO.std.take( null ); 1129 | // [] 1130 | ``` 1131 | 1132 | * **See Also:** [`FPO.std.head(..)`](#fpostdhead) 1133 | 1134 | ---- 1135 | 1136 | ### `FPO.std.trampoline(..)` 1137 | 1138 | ([back to top](#standard-api)) 1139 | 1140 | Wraps a continuation-returning recursive function in another function that will run it until it no longer returns another continuation function. Trampolines are an alternative to tail calls. 1141 | 1142 | * **Arguments:** 1143 | - `fn`: function to run 1144 | 1145 | * **Returns:** *function* 1146 | 1147 | * **Example:** 1148 | 1149 | ```js 1150 | function sum(total,x) { 1151 | if (x <= 1) return total + x; 1152 | return () => sum( total + x, x - 1 ); 1153 | } 1154 | 1155 | var f = FPO.std.trampoline( sum ) 1156 | 1157 | f( 0, 5 ); 1158 | // 15 1159 | ``` 1160 | 1161 | ---- 1162 | 1163 | ### `FPO.std.transducers.array(..)` 1164 | 1165 | ([back to top](#standard-api)) 1166 | 1167 | A reducer function. For transducing purposes, a combination function that takes an array and a value, and mutates the array by pushing the value onto the end of it. The mutated array is returned. 1168 | 1169 | **This function has side-effects**, for performance reasons. It should be used with caution. 1170 | 1171 | * **Arguments:** 1172 | - `acc`: acculumator 1173 | - `v`: value 1174 | 1175 | * **Returns:** *array* 1176 | 1177 | * **Example:** 1178 | 1179 | ```js 1180 | var arr = [1,2,3]; 1181 | 1182 | FPO.std.transducers.array( arr, 4 ); 1183 | // [1,2,3,4] 1184 | 1185 | arr; 1186 | // [1,2,3,4] <-- was mutated as a side-effect! 1187 | ``` 1188 | 1189 | * **See Also:** [`FPO.std.transducers.booleanAnd(..)`](#fpostdtransducersbooleanand), 1190 | * [`FPO.std.transducers.booleanOr(..)`](#fpostdtransducersbooleanor), [`FPO.std.transducers.default(..)`](#fpostdtransducersdefault), [`FPO.std.transducers.number(..)`](#fpostdtransducersnumber), [`FPO.std.transducers.string(..)`](#fpostdtransducersstring) 1191 | 1192 | ---- 1193 | 1194 | ### `FPO.std.transducers.booleanAnd(..)` 1195 | 1196 | ([back to top](#standard-api)) 1197 | 1198 | A reducer function. For transducing purposes, a combination function that takes two booleans and *AND*s them together. The result is the logical *AND* of the two values. 1199 | 1200 | * **Arguments:** 1201 | - `acc`: acculumator 1202 | - `v`: value 1203 | 1204 | * **Returns:** *true/false* 1205 | 1206 | * **Example:** 1207 | 1208 | ```js 1209 | FPO.std.transducers.booleanAnd( true, true ); 1210 | // true 1211 | 1212 | FPO.std.transducers.booleanAnd( false, true ); 1213 | // false 1214 | ``` 1215 | 1216 | * **Aliases:** `FPO.std.transducers.boolean(..)` 1217 | 1218 | * **See Also:** [`FPO.std.transducers.array(..)`](#fpostdtransducersarray), 1219 | * [`FPO.std.transducers.booleanOr(..)`](#fpostdtransducersbooleanor), [`FPO.std.transducers.default(..)`](#fpostdtransducersdefault), [`FPO.std.transducers.number(..)`](#fpostdtransducersnumber), [`FPO.std.transducers.string(..)`](#fpostdtransducersstring) 1220 | 1221 | ---- 1222 | 1223 | ### `FPO.std.transducers.booleanOr(..)` 1224 | 1225 | ([back to top](#standard-api)) 1226 | 1227 | A reducer function. For transducing purposes, a combination function that takes two booleans and *OR*s them together. The result is the logical *OR* of the two values. 1228 | 1229 | * **Arguments:** 1230 | - `acc`: acculumator 1231 | - `v`: value 1232 | 1233 | * **Returns:** *true/false* 1234 | 1235 | * **Example:** 1236 | 1237 | ```js 1238 | FPO.std.transducers.booleanOr( false, true ); 1239 | // true 1240 | 1241 | FPO.std.transducers.booleanOr( false, false ); 1242 | // false 1243 | ``` 1244 | 1245 | * **See Also:** [`FPO.std.transducers.array(..)`](#fpostdtransducersarray), 1246 | * [`FPO.std.transducers.booleanAnd(..)`](#fpostdtransducersbooleanand), [`FPO.std.transducers.default(..)`](#fpostdtransducersdefault), [`FPO.std.transducers.number(..)`](#fpostdtransducersnumber), [`FPO.std.transducers.string(..)`](#fpostdtransducersstring) 1247 | 1248 | ---- 1249 | 1250 | ### `FPO.std.transducers.default(..)` 1251 | 1252 | ([back to top](#standard-api)) 1253 | 1254 | A reducer function. For transducing purposes, a combination function that's a default placeholder. It returns only the `acc` that's passed to it. The behavior here is almost the same as [`FPO.std.identity(..)`](#fpostdidentity), except that returns `acc` instead of `v`. 1255 | 1256 | * **Arguments:** 1257 | - `acc`: acculumator 1258 | - `v`: value 1259 | 1260 | * **Returns:** *-any-* 1261 | 1262 | * **Example:** 1263 | 1264 | ```js 1265 | FPO.std.transducers.default( 3, 1 ); 1266 | // 3 1267 | ``` 1268 | 1269 | * **See Also:** [`FPO.std.transducers.array(..)`](#fpostdtransducersarray), 1270 | * [`FPO.std.transducers.booleanAnd(..)`](#fpostdtransducersbooleanand), [`FPO.std.transducers.booleanOr(..)`](#fpostdtransducersbooleanOr), [`FPO.std.transducers.number(..)`](#fpostdtransducersnumber), [`FPO.std.transducers.string(..)`](#fpostdtransducersstring), [`FPO.std.identity(..)`](#fpostdidentity) 1271 | 1272 | ---- 1273 | 1274 | ### `FPO.std.transducers.filter(..)` 1275 | 1276 | ([back to top](#standard-api)) 1277 | 1278 | For transducing purposes, wraps a predicate function as a filter-transducer. Typically, this filter-transducer is then composed with other filter-transducers and/or map-transducers. The resulting transducer is then passed to [`FPO.std.transducers.transduce(..)`](#fpostdtransducerstransduce). 1279 | 1280 | The filter-transducer is not a reducer itself; it's expecting a combination function (reducer), which will then produce a filter-reducer. So alternately, you can manually create the filter-reducer and use it directly with a regular [`FPO.std.reduce(..)`](#fpostdreduce) reduction. 1281 | 1282 | * **Arguments:** 1283 | - `fn`: predicate function 1284 | 1285 | * **Returns:** *function* 1286 | 1287 | * **Example:** 1288 | 1289 | ```js 1290 | function isOdd(v) { return v % 2 == 1; } 1291 | function arrayPush(acc,v) { acc.push( v ); return acc; } 1292 | 1293 | var nums = [1,2,3,4,5]; 1294 | 1295 | var filterTransducer = FPO.std.transducers.filter( isOdd ); 1296 | 1297 | FPO.std.transducers.transduce( filterTransducer, arrayPush, [], nums ); 1298 | // [1,3,5] 1299 | 1300 | // ****************** 1301 | 1302 | var filterReducer = filterTransducer( arrayPush ); 1303 | 1304 | filterReducer( [], 3 ); 1305 | // [3] 1306 | 1307 | filterReducer( [], 4 ); 1308 | // [] 1309 | 1310 | FPO.std.reduce( filterReducer, [], nums ); 1311 | // [1,3,5] 1312 | ``` 1313 | 1314 | * **See Also:** [`FPO.std.transducers.map(..)`](#fpostdtransducersmap) 1315 | 1316 | ---- 1317 | 1318 | ### `FPO.std.transducers.into(..)` 1319 | 1320 | ([back to top](#standard-api)) 1321 | 1322 | Selects an appropriate combination function (reducer) based on the provided initial value. Then runs [`FPO.std.transducers.transduce(..)`](#fpostdtransducerstransduce) under the covers. 1323 | 1324 | Detects initial values of `boolean`, `number`, `string`, and `array` types, and dispatches to the appropriate combination function accordingly ([`FPO.std.transducers.number(..)`](#fpostdtransducersnumber), etc). **Note:** A `boolean` initial value selects [`FPO.std.transducers.booleanAnd(..)`](#fpostdtransducersboooleanand). 1325 | 1326 | **Note:** When composing transducers, the effective order of operations is reversed from normal composition. Instead of expecting composition to be right-to-left, the effective order will be left-to-right (see below). 1327 | 1328 | * **Arguments:** 1329 | - `fn`: transducer function 1330 | - `v`: initial value for the reduction; also used to select the appropriate combination function (reducer) for the transducing. 1331 | - `arr`: the list for the reduction 1332 | 1333 | * **Returns:** *-any-* 1334 | 1335 | * **Example:** 1336 | 1337 | ```js 1338 | function double(v) { return v * 2; } 1339 | function isOdd(v) { return v % 2 == 1; } 1340 | 1341 | var nums = [1,2,3,4,5]; 1342 | 1343 | var transducer = FPO.std.compose( [ 1344 | FPO.std.transducers.filter( isOdd ), 1345 | FPO.std.transducers.map( double ) 1346 | ] ); 1347 | 1348 | FPO.std.transducers.into( transducer, [], nums ); 1349 | // [2,6,10] 1350 | 1351 | FPO.std.transducers.into( transducer, 0, nums ); 1352 | // 18 1353 | 1354 | FPO.std.transducers.into( transducer, "", nums ); 1355 | // "2610" 1356 | ``` 1357 | 1358 | * **See Also:** [`FPO.std.transducers.transduce(..)`](#fpostdtransducerstransduce) 1359 | 1360 | ---- 1361 | 1362 | ### `FPO.std.transducers.map(..)` 1363 | 1364 | ([back to top](#standard-api)) 1365 | 1366 | For transducing purposes, wraps a mapper function as a map-transducer. Typically, this map-transducer is then composed with other filter-transducers and/or map-transducers. The resulting transducer is then passed to [`FPO.std.transducers.transduce(..)`](#fpostdtransducerstransduce). 1367 | 1368 | The map-transducer is not a reducer itself; it's expecting a combination function (reducer), which will then produce a filter-reducer. So alternately, you can manually create the map-reducer and use it directly with a regular [`FPO.std.reduce(..)`](#fpostdreduce) reduction. 1369 | 1370 | * **Arguments:** 1371 | - `fn`: mapper function 1372 | 1373 | * **Returns:** *function* 1374 | 1375 | * **Example:** 1376 | 1377 | ```js 1378 | function double(v) { return v * 2; } 1379 | function arrayPush(acc,v) { acc.push( v ); return acc; } 1380 | 1381 | var nums = [1,2,3,4,5]; 1382 | 1383 | var mapTransducer = FPO.std.transducers.map( double ); 1384 | 1385 | FPO.std.transducers.transduce( mapTransducer, arrayPush, [], nums ); 1386 | // [2,4,6,8,10] 1387 | 1388 | // ****************** 1389 | 1390 | var mapReducer = mapTransducer( arrayPush ); 1391 | 1392 | mapReducer( [], 3 ); 1393 | // [6] 1394 | 1395 | FPO.std.reduce( mapReducer, [], nums ); 1396 | // [2,4,6,8,10] 1397 | ``` 1398 | 1399 | * **See Also:** [`FPO.std.transducers.filter(..)`](#fpostdtransducersfilter) 1400 | 1401 | ---- 1402 | 1403 | ### `FPO.std.transducers.number(..)` 1404 | 1405 | ([back to top](#standard-api)) 1406 | 1407 | A reducer function. For transducing purposes, a combination function that adds together the two numbers passed into it. The result is the sum. 1408 | 1409 | * **Arguments:** 1410 | - `acc`: acculumator 1411 | - `v`: value 1412 | 1413 | * **Returns:** *number* 1414 | 1415 | * **Example:** 1416 | 1417 | ```js 1418 | FPO.std.transducers.number( 3, 4 ); 1419 | // 7 1420 | ``` 1421 | 1422 | * **See Also:** [`FPO.std.transducers.array(..)`](#fpostdtransducersarray), 1423 | * [`FPO.std.transducers.booleanAnd(..)`](#fpostdtransducersbooleanand), [`FPO.std.transducers.booleanOr(..)`](#fpostdtransducersbooleanOr), [`FPO.std.transducers.default(..)`](#fpostdtransducersdefault), [`FPO.std.transducers.string(..)`](#fpostdtransducersstring) 1424 | 1425 | ---- 1426 | 1427 | ### `FPO.std.transducers.string(..)` 1428 | 1429 | ([back to top](#standard-api)) 1430 | 1431 | A reducer function. For transducing purposes, a combination function that concats the two strings passed into it. The result is the concatenation. 1432 | 1433 | * **Arguments:** 1434 | - `acc`: acculumator 1435 | - `v`: value 1436 | 1437 | * **Returns:** *string* 1438 | 1439 | * **Example:** 1440 | 1441 | ```js 1442 | FPO.std.transducers.string( "hello", "world" ); 1443 | // "helloworld" 1444 | ``` 1445 | 1446 | * **See Also:** [`FPO.std.transducers.array(..)`](#fpostdtransducersarray), 1447 | * [`FPO.std.transducers.booleanAnd(..)`](#fpostdtransducersbooleanand), [`FPO.std.transducers.booleanOr(..)`](#fpostdtransducersbooleanOr), [`FPO.std.transducers.default(..)`](#fpostdtransducersdefault), [`FPO.std.transducers.number(..)`](#fpostdtransducersnumber) 1448 | 1449 | ---- 1450 | 1451 | ### `FPO.std.transducers.transduce(..)` 1452 | 1453 | ([back to top](#standard-api)) 1454 | 1455 | Produces a reducer from a specified transducer and combination function. Then runs a reduction on a list, using that reducer, starting with the specified initial value. 1456 | 1457 | **Note:** When composing transducers, the effective order of operations is reversed from normal composition. Instead of expecting composition to be right-to-left, the effective order will be left-to-right (see below). 1458 | 1459 | * **Arguments:** 1460 | - `fn`: transducer function 1461 | - `co`: combination function for the transducer 1462 | - `v`: initial value for the reduction 1463 | - `arr`: the list for the reduction 1464 | 1465 | * **Returns:** *-any-* 1466 | 1467 | * **Example:** 1468 | 1469 | ```js 1470 | function double(v) { return v * 2; } 1471 | function isOdd(v) { return v % 2 == 1; } 1472 | function arrayPush(acc,v) { acc.push( v ); return acc; } 1473 | 1474 | var nums = [1,2,3,4,5]; 1475 | 1476 | var transducer = FPO.std.compose( [ 1477 | FPO.std.transducers.filter( isOdd ), 1478 | FPO.std.transducers.map( double ) 1479 | ] ); 1480 | 1481 | FPO.std.transducers.transduce( transducer, arrayPush, [], nums ); 1482 | // [2,6,10] 1483 | ``` 1484 | 1485 | * **See Also:** [`FPO.std.transducers.into(..)`](#fpostdtransducersinto) 1486 | 1487 | ---- 1488 | 1489 | ### `FPO.std.unapply(..)` 1490 | 1491 | ([back to top](#standard-api)) 1492 | 1493 | Wraps a function to gather individual positional arguments into an array argument. 1494 | 1495 | * **Arguments:** 1496 | - `fn`: function to wrap 1497 | - `props`: list of property names (strings) to indicate the order to gather individual positional arguments as properties. 1498 | 1499 | * **Returns:** *function* 1500 | 1501 | * **Example:** 1502 | 1503 | ```js 1504 | function foo([ x,y ]) { return x + y; } 1505 | 1506 | var f = FPO.std.unapply( foo ); 1507 | 1508 | f( 1, 2 ); // 3 1509 | ``` 1510 | 1511 | * **Aliases:** `FPO.std.gather(..)` 1512 | 1513 | * **See Also:** [`FPO.std.apply(..)`](#fpostdapply) 1514 | 1515 | ---- 1516 | 1517 | ### `FPO.std.unary(..)` 1518 | 1519 | ([back to top](#standard-api)) 1520 | 1521 | Wraps a function to restrict its inputs to only one argument. 1522 | 1523 | * **Arguments:** 1524 | - `fn`: function to wrap 1525 | 1526 | * **Returns:** *function* 1527 | 1528 | * **Example:** 1529 | 1530 | ```js 1531 | function foo(...args) { return args; } 1532 | 1533 | var f = FPO.std.unary( foo ); 1534 | 1535 | f( 1, 2, 3 ); // [1] 1536 | ``` 1537 | 1538 | * **See Also:** [`FPO.std.nAry(..)`](#fpostdnary), [`FPO.std.binary(..)`](#fpostdbinary) 1539 | 1540 | ---- 1541 | 1542 | ### `FPO.std.uncurry(..)` 1543 | 1544 | ([back to top](#standard-api)) 1545 | 1546 | Wraps a (strictly) curried function in a new function that accepts all the arguments at once, and provides them one at a time to the underlying curried function. 1547 | 1548 | * **Arguments:** 1549 | - `fn`: function to uncurry 1550 | 1551 | * **Returns:** *function* 1552 | 1553 | * **Example:** 1554 | 1555 | ```js 1556 | function foo(x,y,z) { return x + y + z; } 1557 | 1558 | var f = FPO.std.curry( foo ); 1559 | 1560 | var p = FPO.std.uncurry( f ); 1561 | 1562 | p( 1, 2, 3 ); // 6 1563 | ``` 1564 | 1565 | * **See Also:** [`FPO.std.curryMultiple(..)`](#fpostdcurrymultiple), [`FPO.std.partial(..)`](#fpostdpartial), [`FPO.std.uncurry(..)`](#fpostduncurry) 1566 | 1567 | ---- 1568 | 1569 | ### `FPO.std.zip(..)` 1570 | 1571 | ([back to top](#standard-api)) 1572 | 1573 | Produces a new array where each element is an array containing the value from that position in each of the respective input arrays. Only processes up to the shorter of the two arrays. 1574 | 1575 | * **Arguments:** 1576 | - `arr1`: the first array 1577 | - `arr2`: the second array 1578 | 1579 | * **Returns:** *array* 1580 | 1581 | * **Example:** 1582 | 1583 | ```js 1584 | var nums1 = [1,3,5,7]; 1585 | var nums2 = [2,4,6]; 1586 | 1587 | FPO.std.zip( nums1, nums2 ); 1588 | // [[1,2],[3,4],[5,6]] 1589 | ``` 1590 | 1591 | * **See Also:** [`FPO.std.flatten(..)`](#fpostdflatten) 1592 | --------------------------------------------------------------------------------