• This repository has been archived on 31/Dec/2022
  • Stars
    star
    449
  • Rank 97,328 (Top 2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 8 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

FP library for JavaScript. Supports named-argument style methods.

FPO

Build Status npm Module Coverage Status Modules License

FPO (/ˈefpō/) is an FP Library for JavaScript. The main aesthetic difference is that the FPO.* core API methods are all styled to use named-arguments (object parameter destructuring) instead of individual positional arguments.

// positional arguments
foo( 1, 2, 3 );

// named arguments
foo({ z: 3, x: 1, y: 2 });

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.

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!

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(..) and FPO.unapply(..) methods.

For convenience and familiarity sake, FPO also exposes all its methods in the traditional positional argument form, under FPO.std.*. These methods are very similar to their equivalents in Ramda, for example.

Note: If you're not fully confident in your FP skills, I've written a book on FP in JavaScript: Functional-Light JavaScript. Go check it out!

Environment Support

This library uses ES6+ features. If you need to support ES<=5 environments, transpile it first (with Babel, etc).

At A Glance

// the classic/traditional method style
// (on the `FPO.std.*` namespace)
FPO.std.reduce(
    (acc,v) => acc + v,
    undefined,
    [3,7,9]
);  // 19

// FPO named-argument method style
FPO.reduce({
    arr: [3,7,9],
    fn: ({acc,v}) => acc + v
}); // 19

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!

As with most FP libraries, all public FPO methods are curried. Currying with named-arguments (in any sequence!) is a breeze:

var f = FPO.reduce({ arr: [3,7,9] });

// later:
f( {fn: ({acc,v}) => acc + v} );  // 19
f( {fn: ({acc,v}) => acc * v} );  // 189

The equivalent using a standard FP library would look like:

var f = curry( flip( partialRight( reduce, [[3,7,9]] ) ) )( 0 );

f( (acc,v) => acc + v );  // 19

Phew, that's a lot of argument juggling! FPO eliminates all that noisy distraction.

API

  • See Core API for documentation on all the methods in the FPO.* namespace.

  • See Standard API for documenation on the methods in the FPO.std.* namespace.

    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.

    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.

Adapting

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(..):

// traditional-parameter style function
function add(acc,v) { return acc + v; }

FPO.reduce({
    arr: [3,7,9],
    fn: FPO.apply( {fn: add} )
});  // 19

Adapting isn't limited to just interoperating with FPO methods. You can use FPO.apply(..) and FPO.unapply(..) to seamlessly adapt between functions of both styles in your own application code:

// note: cb is expected to be an "error-first" style
// traditional callback function
function ajax(url,cb) { .. }

// our object-parameter style function
function onResponse({ resp, err }) {
    if (!err) {
        console.log( resp );
    }
}

// adapt `ajax(..)` to accept named arguments
var request = FPO.apply( {fn: ajax} );

// now we can provide arguments in any order!
request({
    cb:
        // adapt the object-parameter style `onResponse(..)`
        // to work as a standard positional-argument function, as
        // expected by `ajax(..)`
        FPO.unapply( {fn: onResponse, props:["err","resp"]} ),

    url: "http://some.url"
});

Remapping Function Parameters/Arguments

What if you have a function that expects a certain named parameter, but it needs to accept a differently named argument? For example:

function uppercase({ str }) { return str.toUpperCase(); }

FPO.map( {fn: uppercase, arr: ["hello","world"]} );
// TypeError (`str` is undefined)

The problem here is that FPO.map(..) expects to call its mapper function with a v named argument, but uppercase(..) expects str.

FPO.remap(..) -- despite the name similarity, no particular relationship to FPO.map(..), except that it's our example -- lets you adapt a function to remap its expected named parameters:

function uppercase({ str }) { return str.toUpperCase(); }

FPO.map( {
    fn: FPO.remap( {fn: uppercase, args: {str: "v"}} ),
    arr: ["hello","world"]
} );
// ["HELLO","WORLD"]

Note: The FPO.remap(..) utility passes through any non-remapped arguments as-is.

Not Order, But Names

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(..) expects named arguments of fn, v, and arr. If you don't use those names, it won't work correctly.

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.

The named argument naming conventions (in order of precedence):

  • When a method expects a function, the named argument is fn.
  • When a method expects a number, the named argument is n.
  • When a method expects a value, the named argument is v.
  • When a method expects an array of functions, the named argument is fns.
  • When a method expects a single array, the named argument is arr.
  • When a method expects two arrays, the named arguments are arr1 and arr2.
  • When a method expects a single object-property name, the named argument is prop.
  • When a method expects a list of object-property names, the named argument is props.
  • When a mapper function is called, it will be passed these named arguments: v (value), i (index), arr (array).
  • When a predicate function is called, it will be passed these named arguments: v (value), i (index), arr (array).
  • When a reducer function is called, it will be passed these named arguments: acc (accumulator), v (value), i (index), arr (array).
  • When a transducer combination function is called, it will be passed these named arguments: acc (accumulator), v (value).

Some exceptions to these naming conventions:

  • FPO.setProp(..) expects: prop (object-property name), o (object), v (value).

  • FPO.partial(..) expects: fn (function), args (object containing the named-argument/value pairs to partially apply).

  • FPO.flatten(..) expects: v (array), n (count of nesting levels to flatten out).

  • FPO.transduce(..) expects: fn (transducer function), co (combination function), v (initial value), arr (array).

  • FPO.compose(..) and FPO.pipe(..) 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.

    This also applies to transducers. FPO.transducers.filter(..) and FPO.transducers.map(..), whether composed together or used standalone, are curried to expect the combination function to be passed to them as a { v: .. }-style object argument.

  • FPO.reassoc(..) expects: props (object with sourceProp: targetProp remapping key/value pairs), v (object)

Arity

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.

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.

For currying, FPO assumes the straight numeric count interpretation. FPO.curry( {fn: foo, n: 3} ) makes a curried function that accepts the first 3 properties, one at a time, regardless of what they're named.

For unary(..), binary(..), and nAry(..), 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(..).

Currying

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.

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)).

FPO supports both approaches. FPO.curry(..) (and FPO.std.curry(..)) use the stricter one-at-a-time definition -- subsequent arguments in each call are ignored -- while FPO.curryMultiple(..) (and FPO.std.curryMultiple(..)) use the looser multiple-arguments definition.

All FPO methods are multiple-curried for convenience.

Builds

Build Status npm Module

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.

However, if you download this repository via Git:

  1. The included build utility (scripts/build-core.js) builds (and minifies) dist/fpo.js from source. The build utility expects Node.js version 6+.

  2. To install the build and test dependencies, run npm install from the project root directory.

  3. Because of how npm lifecycle 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.

To run the build utility with npm:

npm run build

To run the build utility directly without npm:

node scripts/build-core.js

Tests

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.

  1. You can run the tests in a browser by opening up tests/index.html (requires ES6+ browser environment).

  2. The included Node.js test utility (scripts/node-tests.js) runs the test suite. This test utility expects Node.js version 6+.

  3. Ensure the Node.js test utility dependencies are installed by running npm install from the project root directory.

  4. Because of how npm lifecycle 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.

To run the test utility with npm:

npm test

Other npm test scripts:

  • npm run test:dist will run the test suite against dist/fpo.js.

  • 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.

  • 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.

To run the test utility directly without npm:

node scripts/node-tests.js

Test Coverage

Coverage Status

If you have Istanbul already installed on your system (requires v1.0+), you can use it to check the test coverage:

npm run coverage

Then open up coverage/lcov-report/index.html in a browser to view the report.

To run Istanbul directly without npm:

istanbul cover scripts/node-tests.js

Note: The npm script coverage:report is only intended for use by project maintainers. It sends coverage reports to Coveralls.

License

License

All code and documentation are (c) 2019 Kyle Simpson and released under the MIT License. A copy of the MIT License is also included.

More Repositories

1

You-Dont-Know-JS

A book series on JavaScript. @YDKJS on twitter.
178,746
star
2

Functional-Light-JS

Pragmatic, balanced FP in JavaScript. @FLJSBook on twitter.
JavaScript
16,593
star
3

LABjs

Loading And Blocking JavaScript: On-demand parallel loader for JavaScript with execution order dependencies
HTML
2,277
star
4

asynquence

Asynchronous flow control (promises, generators, observables, CSP, etc)
JavaScript
1,743
star
5

CAF

Cancelable Async Flows (CAF)
JavaScript
1,332
star
6

monio

The most powerful IO monad implementation in JS, possibly in any language!
JavaScript
1,049
star
7

TNG-Hooks

Provides React-inspired 'hooks' like useState(..) for stand-alone functions
JavaScript
1,011
star
8

native-promise-only

A polyfill for native ES6 Promises as close as possible (no extensions) to the strict spec definitions.
JavaScript
723
star
9

A-Tale-Of-Three-Lists

Comparing various async patterns for a single demo
JavaScript
651
star
10

fasy

FP iterators that are both eager and asynchronous
JavaScript
546
star
11

youperiod.app

YouPeriod.app -- the privacy-first period tracking app
JavaScript
442
star
12

JSON.minify

Simple minifier for JSON to remove comments and whitespace
409
star
13

TypL

The Type Linter for JS
JavaScript
374
star
14

h5ive-DEPRECATED

**DEPRECATED** A collection of thin facade APIs wrapped around HTML5 JavaScript features.
JavaScript
324
star
15

eslint-plugin-proper-arrows

ESLint rules to ensure proper arrow function definitions
JavaScript
305
star
16

foi-lang

Foi: a different kind of functional programming language
JavaScript
303
star
17

grips

Simple-logic templates
JavaScript
287
star
18

moduloze

Convert CommonJS (CJS) modules to UMD and ESM formats
JavaScript
207
star
19

ES-Feature-Tests

Feature Tests for JavaScript
JavaScript
199
star
20

let-er

DEPRECATED: Transpile non-ES6 let-blocks into ES6 (or ES3)
JavaScript
192
star
21

Incomplete-JS

"The Incomplete Guide to JavaScript" (book). @IncompleteJS on twitter.
190
star
22

revocable-queue

Specialized async queue data structure, supports revocation of values
JavaScript
176
star
23

deePool

highly-efficient pool for JavaScript objects
JavaScript
118
star
24

getify.github.io

JavaScript
113
star
25

concrete-syntax-tree

Defining a standard JavaScript CST (concrete syntax tree) to complement ASTs
107
star
26

eslint-plugin-proper-ternary

ESLint rules to ensure proper usage of ternary/conditional expressions
JavaScript
96
star
27

cloud-sweeper

A casual game built for the web.
JavaScript
94
star
28

BikechainJS

JavaScript VM engine (powered by V8); server-side environment modules; server-side synchronous web app controllers
C++
82
star
29

wepuzzleit

Demo PoC game for various advanced HTML5 js APIs
JavaScript
79
star
30

workshop-periodic-table

66
star
31

elasi

EL/ASI: Encrypt Locally, Account Secure Interchange
JavaScript
62
star
32

remote-csp-channel

Remote bridge for CSP channels
JavaScript
55
star
33

ScanTree

Scan a JS file tree to build an ordered and grouped dependency listing
JavaScript
51
star
34

dwordly-game

A game where words dwindle down to the shortest possible
JavaScript
42
star
35

stable-timers

timers with less race conditions
JavaScript
38
star
36

emdash

Simple blogging with node/iojs + GitHub.
36
star
37

domio

DOM and Event helpers for Monio
JavaScript
30
star
38

eslint-plugin-no-optional-call

ESLint plugin to disallow the optional-call operator
JavaScript
30
star
39

eslint-plugin-arrow-require-this

DEPRECATED: ESLint rule to require arrow functions to reference the 'this' keyword
JavaScript
28
star
40

import-remap

Rewrite ES module import specifiers using an import-map.
JavaScript
25
star
41

gum-try-hd

Try to enforce HD (16:9) camera aspect ratio for web-video calls
JavaScript
25
star
42

Mock-DOM-Resources

A mock of (parts of) the DOM API to simulate resource preloading and loading
JavaScript
25
star
43

make-a-game

some project files for a tutorial on making a simple web game
JavaScript
25
star
44

mpAjax

framework plugin for handling multi-part Ajax responses
JavaScript
23
star
45

asyncGate.js

DEPRECATED. asynchronous gate for javascript
JavaScript
21
star
46

tic-tac-toe-workshop

Workshop files for building tic-tac-toe in JS and <canvas>
21
star
47

esre

esre: fully configurable JS code formatting
20
star
48

workshop-chess-diagonals

17
star
49

featuretests.io

JavaScript Feature Tests... as a service
JavaScript
16
star
50

FoilScript

FoilScript: a new dialect of JS that fixes the sucky parts but still looks and feels like JS
16
star
51

literalizer

Specialized heuristic lexer for JS to identify complex literals
JavaScript
16
star
52

normalize-selector

Normalize CSS selectors
JavaScript
14
star
53

DOMEventBridge

Bridge DOM events to a JS event hub (for pubsub)
JavaScript
14
star
54

pong-around-workshop

Workshop files for building a pong-variant game in JS and <canvas>
12
star
55

workshop-wordy-unscrambler

11
star
56

shortie.me

Proof-of-concept demo for server-side JavaScript driven "middle-end" architecture
JavaScript
11
star
57

workshop-knights-dialer

8
star
58

middleend-boilerplate

Boilerplate Starting Point for Middle-end Web Architecture
JavaScript
8
star
59

demo-app-weatheround

JavaScript
7
star
60

the-economy-of-keystrokes-slides

Slides code built for "The Economy of Keystrokes" talk
JavaScript
6
star
61

santa-connect-app

Santa Connect: keeping track of your kids' nice/naughty status
JavaScript
5
star
62

nyc-bug-demo

bug demo for NYC code coverage tool
JavaScript
4
star
63

unnamed

unnamed (for now). nothing to see here. ;-)
JavaScript
2
star
64

my-lifesheets

CSS
1
star
65

breakthewebforward.com

JavaScript
1
star