Comparing version 0.3.2 to 1.1.1
{ | ||
"name": "ferrum", | ||
"version": "0.3.2", | ||
"version": "1.1.1", | ||
"description": "", | ||
"main": "src/index.js", | ||
"scripts": { | ||
"test": " nyc --reporter=text --reporter=lcov --check-coverage --branches 90 --statements 90 --lines 90 mocha", | ||
"test": "nyc --reporter=text --reporter=lcov --check-coverage --branches 90 --statements 90 --lines 90 mocha --reporter xunit --reporter-options output=./junit/test-results.xml test src", | ||
"test-ci": "npm run test && codecov", | ||
"lint": "./node_modules/.bin/eslint src test", | ||
"junit": "mocha --exit -R mocha-junit-reporter", | ||
"preversion": "npm run junit", | ||
"postversion": "git push origin master --follow-tags", | ||
"delete-git-tag": "git tag -d v$npm_package_version && git push origin :v$npm_package_version", | ||
"doc": "jsdoc -c .jsdoc.json" | ||
"semantic-release": "semantic-release", | ||
"docs": "jsdoc -c .jsdoc.json", | ||
"commit": "git-cz", | ||
"snyk-protect": "snyk protect", | ||
"prepare": "npm run snyk-protect" | ||
}, | ||
@@ -26,22 +27,34 @@ "repository": { | ||
"dependencies": { | ||
"lodash.isplainobject": "^4.0.6" | ||
"lodash.isplainobject": "4.0.6", | ||
"npm-check-updates": "^3.1.12" | ||
}, | ||
"devDependencies": { | ||
"ajv": "^6.10.0", | ||
"codecov": "^3.5.0", | ||
"@semantic-release/changelog": "3.0.4", | ||
"@semantic-release/commit-analyzer": "6.2.0", | ||
"@semantic-release/git": "7.0.12", | ||
"@semantic-release/github": "5.4.0", | ||
"@semantic-release/npm": "5.1.9", | ||
"@semantic-release/release-notes-generator": "7.2.0", | ||
"ajv": "6.10.0", | ||
"codecov": "3.5.0", | ||
"commitizen": "3.1.1", | ||
"cz-conventional-changelog": "2.1.0", | ||
"docdash": "git+https://github.com/koraa/docdash.git", | ||
"eslint": "^5.16.0", | ||
"eslint-config-airbnb": "^17.1.0", | ||
"eslint-plugin-header": "^3.0.0", | ||
"eslint-plugin-import": "^2.17.3", | ||
"eslint-plugin-jsx-a11y": "^6.2.1", | ||
"eslint-plugin-react": "^7.13.0", | ||
"jsdoc": "^3.6.2", | ||
"junit-report-builder": "^1.3.2", | ||
"mocha": "^6.1.4", | ||
"mocha-junit-reporter": "^1.23.0", | ||
"mocha-parallel-tests": "^2.2.0", | ||
"nyc": "^14.1.1" | ||
"eslint": "6.0.1", | ||
"eslint-config-airbnb": "17.1.0", | ||
"eslint-plugin-header": "3.0.0", | ||
"eslint-plugin-import": "2.18.0", | ||
"eslint-plugin-jsx-a11y": "6.2.1", | ||
"eslint-plugin-react": "7.14.2", | ||
"jsdoc": "3.6.2", | ||
"junit-report-builder": "1.3.2", | ||
"lint-staged": "8.2.1", | ||
"mocha": "6.1.4", | ||
"mocha-junit-reporter": "1.23.0", | ||
"mocha-parallel-tests": "2.2.1", | ||
"nyc": "14.1.1", | ||
"semantic-release": "15.13.16", | ||
"snyk": "1.183.0" | ||
}, | ||
"snyk": true | ||
} |
598
README.md
@@ -0,1 +1,599 @@ | ||
<a name="ferrum"></a> | ||
# Ferrum | ||
Features from the rust language in javascript: Provides Traits/Type classes & an advanced library for working with sequences/iterators in js. | ||
[Github](https://github.com/adobe/ferrum) | ||
[API Documentation](https://www.ferrumjs.org) | ||
<a name="table-of-contents"></a> | ||
## Table of Contents | ||
1. [Introduction](#ferrum) | ||
2. [Table of Contents](#table-of-contents) | ||
3. [Status](#status) | ||
4. [Usage & Features](#usage-features) | ||
1. [Sequence/Iterators](#sequence-iterators) | ||
1. [Objects as Sequences](#objects-as-sequences) | ||
2. [Reverse Currying](#reverse-currying) | ||
3. [Pipelining](#pipelining) | ||
4. [Lazy Evaluation](#lazy-evaluation) | ||
2. [Traits/Typeclasses](#traits-typeclasses) | ||
3. [Operators as functions](#operators-as-functions) | ||
4. [Typing utilities](#typing-utilities) | ||
5. [Functional utilities](#functional-utilities) | ||
5. [Development](#development) | ||
1. [Build](#build) | ||
2. [Test](#test) | ||
2. [Lint](#lint) | ||
<a name="status"></a> | ||
## Status | ||
[![CircleCI](https://img.shields.io/circleci/project/github/adobe/ferrum/master.svg)](https://circleci.com/gh/adobe/ferrum/tree/master) | ||
[![codecov](https://img.shields.io/codecov/c/github/adobe/ferrum.svg)](https://codecov.io/gh/adobe/ferrum) | ||
[![GitHub license](https://img.shields.io/github/license/adobe/ferrum.svg)](https://github.com/adobe/ferrum/blob/master/LICENSE.txt) | ||
[![GitHub issues](https://img.shields.io/github/issues/adobe/ferrum.svg)](https://github.com/adobe/ferrum/issues) | ||
[![LGTM Code Quality Grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/adobe/ferrum.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/adobe/ferrum) | ||
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) | ||
<a name="usage-features"></a> | ||
## Usage & Features | ||
```bash | ||
$ npm add ferrum | ||
``` | ||
<a name="sequence-iterators"></a> | ||
### Sequence/Iterators | ||
| Feature | Ferrum | Underscore | Lodash | wu.js | | ||
| -------------------- | ------ | ---------- | ------ | ----- | | ||
| Objects as Sequences | yes | no | no | no | | ||
| Reverse Currying | yes | no | no | no | | ||
| Lazy Evaluation | yes | no | no | yes | | ||
| Pipelining | yes | no | no | no | | ||
Ferrum provides a library for transforming lists & iterables; it provides all the functions | ||
you would expect like `map`, `filter`, `foldl` and many others. In this regard it is very similar | ||
to libraries like wu.js, lodash or underscore. Ferrum has been written to remedy some of the issues | ||
in these libraries. | ||
<a name="objects-as-sequences"></a> | ||
#### Objects as Sequences | ||
`Ferrum/Sequence` has been designed with full iterator support in mind. Generally all functions | ||
can take iterables/iterators and returns iterators. | ||
```js | ||
const {map, assertSequenceEquals} = require('ferrum'); | ||
const a = map([1,2,3,4], x => x+2); // a is an iterator | ||
const b = map(a, x => x*2); // b is also an iterator | ||
assertSequenceEquals(b, [6, 8, 19, 12]); | ||
``` | ||
In addition to supporting iterables & iterators, `Ferrum/Sequence` can take objects as input: | ||
```js | ||
const {map, iter, assertEquals, assertSequenceEquals} = require('ferrum'); | ||
const a = map({a: 42, b: 43}, ([k, v]) => v+2); // a is an iterator | ||
const b = map(a, x => x*2); // b is also an iterator | ||
assertSequenceEquals(b, [88, 90]); | ||
const obj = {foo: 23, bar: 24}; | ||
const log = []; | ||
for (const [key, value] in iter(obj)) { | ||
log.push(`${key} | ${value}`); | ||
} | ||
assertEquals(log, [ | ||
'foo | 23', | ||
'bar | 24', | ||
]); | ||
``` | ||
`Ferrum/Sequence` uses [lodash.isPlainObject](https://lodash.com/docs/4.17.11#isPlainObject) | ||
and always tries to use the iterator protocol to make sure object iteration is only used if | ||
it really should. | ||
```js | ||
const {map, assertSequenceEquals} = require('ferrum'); | ||
const obj = {}; | ||
obj[Symbol.iterator] = function*() { | ||
yield 2; | ||
yield 3; | ||
}; | ||
assertSequenceEquals(map(obj, x => x*2), [4, 6]); | ||
``` | ||
`Lodash` and `Underscore` only support arrays as input & output; `wu.js` supports iterators as input & output but has no | ||
support for plain objects. | ||
<a name="reverse-currying"></a> | ||
#### Reverse Currying | ||
`Ferrum/Sequence` provides many higher order functions. These are functions that take other functions as parameters, like `map()` or `filter()`. | ||
```js | ||
const {map, filter, assertSequenceEquals} = require('ferrum'); | ||
// Map is used to change each value in a list/iterable | ||
assertSequenceEquals(map([1,2,3,4]), x => x*2, [2,4,6,8]); | ||
// Filter removes elements in a list/iterable | ||
assertSequenceEquals(filter([1,2,3,4]), x => x%2 === 0), [2 4]); | ||
``` | ||
Sometimes it can be useful to create an intermediate function with just a | ||
few arguments instead of calling the function right away: | ||
```js | ||
const myList = [ | ||
[1,2,3], | ||
[4,5,6], | ||
[7,8,9] | ||
]; | ||
// Add 2 to each number in a two dimensional list | ||
// This example uses currying twice: in the `plus(2)` | ||
// and in the `map()` | ||
const a = map(myList, map(plus(2))); | ||
assertSequenceEquals(a, [ | ||
[3,4,5], | ||
[6,7,8], | ||
[9,10,11] | ||
]); | ||
// This is what the code would look like without currying: | ||
// A lot less convenient and harder to read | ||
const b = map(myList, (sublist) => map(sublist, (b) => plus(b, 2))); | ||
assertSequenceEquals(b, [ | ||
[3,4,5], | ||
[6,7,8], | ||
[9,10,11] | ||
]); | ||
``` | ||
You may have noticed, that when currying is used, the arguments are given in reverse order; this is | ||
why we call it reverse currying. We have decided to use currying this way, because there should nevery | ||
be extra arguments after the function (otherwise you end up with dangling arguments multiple lines below) | ||
while the function is usually also the first parameter you want to supply when currying: | ||
```js | ||
const {each} = require('ferrum'); | ||
// This is much more handy | ||
each([1,2,3], () => { | ||
... | ||
}); | ||
// This is not very handy because you might need to scroll down to find the last | ||
// argument; you will also need to scroll down to determine whether the call to | ||
// each is using currying | ||
each(() => { | ||
... | ||
}, [1,2,3]); | ||
``` | ||
Underscore.js does not support currying at all; lodash provides curryied variants of their functions in an extra | ||
module (not very handy either because it is often useful to mix curried and non currying invocations) while lodash | ||
has opted to make the function the first parameter, delivering good support for currying and not so good support | ||
for normal function invocation. | ||
<a name="pipelining"></a> | ||
#### Pipelining | ||
`Ferrum` provides a function called `pipe()` which – together with currying – can be used to build complex data processing pipelines. | ||
Pipelines are conceptually the same as the highly successful pipes in bash; the feature is currently being introduced into the javascript | ||
standard library in the form of the [`|>` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Pipeline_operator). | ||
```js | ||
const {sqrt} = Math; | ||
const {pipe, filter, uniq, map, mul, mapSort, identity, pipe, | ||
prepend, takeWhile, all, range} = require('ferrum'); | ||
const a = pipe( | ||
[5,1,6,7,10,11,1,3,4], | ||
filter(x => x%2 === 1), // Get rid of even number | ||
uniq, // Get rid of duplicates | ||
map(mul(3)), // Multiply each element by three | ||
mapSort(identity)); // Sort all numbers | ||
assertSequenceEquals(q, [3,9,12,15,18,21,30,33]); | ||
// Very simple primality test | ||
const isPrime = (v) => v > 1 && pipe( | ||
// Build the list of candidate factors | ||
range(0, Infinity), // List of all even integers >0 | ||
takeWhile(x => x<=sqrt(v)), // Cut of the list at the square root of our input | ||
// Perform the division test | ||
map(x => v % x === 0), | ||
all); // NOTE: Due to lazy evaluation, dividing will stop at the first factor found | ||
// Sequence of all prime numbers (calculated slowly) | ||
const primes = () => pipe( | ||
range(0, Infinity), // List of all positive integers | ||
filter(isPrime)); | ||
``` | ||
Learning to write algorithms in this way is not always easy, but it can be very rewarding | ||
as the pipe form is often a lot more readable. To illustrate this, let's take a look how our | ||
code of the prime sequence example changes as we take a way features from `Ferrum`; | ||
let's first take away currying and the `pipe()` function itself: | ||
```js | ||
const {sqrt} = Math; | ||
const {all, map, takeWhile, filter, range} = require('ferrum'); | ||
const isPrime = (v) => v > 1 && all(map(takeWhile(range(2, Infinity), x => x<=sqrt(v)), x => v % x === 0)); | ||
const primes = () => filter(range(0, Infinity), isPrime); | ||
``` | ||
One way to work around the lack of currying and `pipe()` is to just put all our | ||
filter stages into one expression. Due to this, our code has become much shorter and much harder to read. | ||
Look at how the dataflow jumps around, see how distant the map function and it's argument are from each other | ||
and it does not help that subexpression cannot be properly documented any more. | ||
Let's try another way to write down these functions: | ||
```js | ||
const {sqrt} = Math; | ||
const {all, map, takeWhile, filter, range} = require('ferrum'); | ||
const positiveIntegers = () => range(1, Infinity); | ||
const isPrime = (v) => { | ||
const fromTwo = range(2, Infinity); | ||
const candidates = takeWhile(fromTwo, x => x<=sqrt(v)); | ||
return v > 1 && all(map(x => v % x === 0)); | ||
} | ||
const primes = () => filter(positiveIntegers(), isPrime); | ||
``` | ||
This is much better! The data flow is more clear and substeps can be documented again. | ||
In this version we used temporary variables to get around not having `pipe()` and currying; | ||
this is much better than just putting everything into one line. | ||
Note how `positiveIntegers` became it's own function while fromTwo and candidates | ||
became just local variables. Also note how all and map are still in the same expression. | ||
Sometimes this is the more readable variant. We have to decide each time. | ||
This variant still has disadvantages though; first of all the code still looks more | ||
cluttered and the dataflow still jumps around more than the pipe variant. | ||
You also have to come up with a lot of names for temporary variables and take care | ||
not to reuse them (since they are lazily evaluated they must only be used once). | ||
This is one of the things you communicate by using `pipe()` over local variables: "This variable | ||
will never be used again" – knowing this & limiting the number of variables in a scope can be | ||
very useful, especially in large functions. | ||
Finally, let's implement this in classic imperative style: | ||
```js | ||
const {sqrt} = Math; | ||
const isPrime = (v) => { | ||
if (v < 2) { | ||
return false; | ||
} | ||
for (let i=2; i <= sqrt(v); i++) { | ||
if (v % i !== 0) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
const primes = function *primes() { | ||
for (let i=2; true; i++) { | ||
if (isPrime(i)) { | ||
yield i; | ||
} | ||
} | ||
} | ||
``` | ||
The first thing that becomes noticeable in this version is that it is more | ||
than twice as long as our variant using `pipe()` (not counting comment lines); | ||
this version also uses two levels of nesting, while our pipelined version uses | ||
just one level of nesting. The imperative version also contains two for loops | ||
and three if statements; for loops are notoriously hard to read as well. | ||
Finally, the imperative version forces us to think in imperative terms – to consider | ||
what happens in each step of the for loop one by one and then come to the conclusion: | ||
*Ah, this for loop just gets rid of all those integers that are not prime*. In the imperative | ||
version this intention must be deduced, in the pipelined version it is plain to see. | ||
To sum it up, using `pipe()` and currying the functions from `Ferrum` has a number | ||
of advantages; you end up with fewer levels of nesting, can avoid a lot of branching (if statements) | ||
and hard to write for loops; pipelining let's you break apart your problem into multiple | ||
clearly defined transformation steps with obvious data flow and obvious intention. | ||
Underscore, lodash and wu all allow you to do something similar with chaining which does work quite well. | ||
They do require a bit more boilerplate since values need to be wrapped before chaining and unwrapped | ||
after chaining has finished. Pipelining will have even less boilerplate when the `|>` becomes | ||
available and pipelining can be used with arbitrary transformation functions, while | ||
chaining can only be used with functions supported by the library, thus pipelining is much | ||
more generic & extensible. | ||
<a name="lazy-evaluation"></a> | ||
#### Lazy Evaluation | ||
Like python iterators, sequences support lazy evaluation. They support it, because lazy evaluation | ||
is a core feature of javascript es6 iterators. | ||
This means that the values in iterators/sequences are only evaluated only once they | ||
are needed: | ||
```js | ||
const {map, plus} = require('ferrum'); | ||
const a = map([1,2,3], plus(2)); // At this point, no calculations have been performed | ||
const b = list(a); // This will actually cause the values of the a iterator to be calculated | ||
``` | ||
Try the above example with a couple of `console.log` statements and see what happens. | ||
The practical upshot of this property is that it becomes possible to work with infinite | ||
sequences, like the `primes()` sequence above. It can be more efficient as well, since | ||
values that are not needed are not computed. | ||
```js | ||
const {take, list, assertEquals} = require('ferrum'); | ||
// Even though primes() is infinite, this just works because only the | ||
// first five primes are actually calculated. | ||
// Note that just list(primes()) would crash the program since that would | ||
// require infinite memory and infinite time | ||
assertEquals(list(take(5, primes())), [2, 3, 5, 7, 11]); | ||
``` | ||
Underscore and lodash use arrays instead of iterators, so they have no lazy evaluation support. | ||
wu uses iterators and thus has full lazy evaluation support. | ||
<a name="traits-typeclasses"></a> | ||
### Traits/Typeclasses | ||
`Sequence/Traits` is the second big feature this library provides; it is a concept borrowed from | ||
the Rust language. They let you declare & document a generic interface; like the `sequence` concept | ||
above they are not an entirely new concept; while sequence is a library designed to make working | ||
with the [javascript iteration protocols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) | ||
easier, traits standardize the creation of javascript protocols itself, thereby reducing boilerplate. | ||
Indeed the `Sequence` Trait is just a wrapper over the `Iterable` protocol of javascript. | ||
```js | ||
const {Size} = require('ferrum'); | ||
// Declaring a trait | ||
/** | ||
* The Size trait is used for any containers that have a known size, like | ||
* arrays, strings, Maps… | ||
* Size should be used only for sizes that are relatively quick to compute, O(1) optimally… | ||
* @interface | ||
*/ | ||
const Size = new Trait('Size'); | ||
// Using it | ||
const size = (what) => Size.invoke(what); | ||
const empty = (what) => size(what) === 0; | ||
// Providing implementations for own types; this implementation will be | ||
// inherited by subclasses | ||
class MyType { | ||
[Size.sym]() { | ||
return 42; | ||
} | ||
} | ||
// Providing implementations for third party types. These won't be inherited | ||
// by subclasses | ||
Size.impl(Array, (x) => x.length); // Method of type Array | ||
Size.impl(String, (x) => x.length); | ||
Size.impl(Map, (x) => x.size); | ||
Size.impl(Set, (x) => x.size); | ||
// This implementation just applies to plain objects. | ||
Size.impl(Object, (x) => { | ||
let cnt = 0; | ||
for (const _ in x) cnt++; | ||
return cnt; | ||
}); | ||
// Note: The two following examples would be a bad idea in reality, | ||
// they are just here toshow the mechanism | ||
Size.implStatic(null, (_) => 0); // Static implementation (for a value and not a type) | ||
``` | ||
Some of the advantages of using Traits are illustrated for the code above: | ||
First of all, using traits saves us a bit of boilerplate code; by having an actual | ||
variable representing the trait, we have a good place to document the trait; the `@interface` | ||
jsdoc feature can be used for this. We can also use this documentation to specify laws that | ||
implementations of the trait should abide by, like the (soft) law that `Size` implementations | ||
should be quick to compute. | ||
Trait also features machinery to implement traits for third party types and even built in types | ||
like Array, Object, null or undefined. The classic way to implement protocols does not work in these | ||
cases: | ||
```js | ||
const Size = Symbol('Size'); | ||
// Using just Symbols works perfectly for your own types | ||
class MyType { | ||
[Size]() { | ||
return 42; | ||
} | ||
} | ||
// Using symbols for third party types is suboptimal, | ||
// since we have to modify the type's prototype which | ||
// could lead to weirdness | ||
Array.prototype[Size] = () => this.length; | ||
// Using symbols for Object, is a very bad idea as the implementation | ||
// will be inheritaed by all other classes…this implementation obviously | ||
// is the wrong one for Set for instance. | ||
// This also illustrates why it is generally a bad idea to enable inheritance | ||
// for third party types | ||
Object.prototype[Size] = () => { | ||
let cnt = 0; | ||
for (const _ in this) cnt++; | ||
return cnt; | ||
} | ||
// Using symbols on values like null or undefined will just lead to a TypeError | ||
// being thrown | ||
null[Size] = () => 0; | ||
``` | ||
The oldest pre-es6 way of implementing just used method names; this strategy is very | ||
problematic, since two different interfaces may use the same method name: | ||
```js | ||
class MyDbTable { | ||
size() { | ||
return request(`https://mydb.com/${this._tableName}/size`); | ||
} | ||
}; | ||
class MyLocalTable { | ||
size() { | ||
return this._payload.length; | ||
} | ||
} | ||
``` | ||
In the hypothetical example above, one size() method returns an integer, while | ||
the other returns a promise resolving an integer (which makes total sense since | ||
it's the size of some database table). Even though each method makes sense for itself, | ||
there is no way distinguishing; they developer may write a function, expecting an integer… | ||
Since the method name `size()` is already taken, we cannot even implement the async | ||
size interface for `MyLocalTable`. | ||
Using traits we can actually encapsulate this relationship well: | ||
```js | ||
// dbtable.js | ||
const { Trait } = require('ferrum'); | ||
const Size = new Trait('Size'); | ||
class DbTable { | ||
[Size.sym]() { | ||
return request(`https://mydb.com/${this._tableName}/size`); | ||
} | ||
}; | ||
module.exports = {Size, DbTable}; | ||
``` | ||
```js | ||
// localtable.js | ||
const { Trait } = require('ferrum'); | ||
const {Size: AsyncSize} = require('./dbtable.js') | ||
const Size = new Trait('Size'); | ||
class MyLocalTable { | ||
[Size.sym]() { | ||
return this._payload.length; | ||
} | ||
} | ||
AsyncSize.implDerived([Size], ([size], v) => Promise.resolve(size(v))); | ||
``` | ||
This example above illustrates how – using traits – we can not only deal with | ||
name collisions by just renaming traits on the fly, we where also able to write | ||
a generic adapter that automatically implements `AsyncSize` for all types supporting `Size`. | ||
To sum up, using Traits provides a number of advantages: Traits let you avoid | ||
some boilerplate code, they allow you to specify and implement generic interfaces | ||
without the danger of name collisions; they let you provide implementations for third | ||
party types, built in types and even null, undefined and plain objects without | ||
modifying these types. | ||
They even let you write generic adapters, implementing traits for entire groups | ||
of traits at once. | ||
<a name="operators-as-functions"></a> | ||
### Operators as functions | ||
`Ferrum/Ops` provides all of the JS operators and some extra boolean operators as curryable functions. | ||
```js | ||
const {plus, and, not, is, xor, map, list} = require('ferrum'); | ||
list(map([1,2,3], plus(2))); // => [2,3,4] | ||
and(True, False); // => False | ||
not(1); // => False | ||
is(2, 2); // => True | ||
xor(True, False); // => True | ||
``` | ||
<a name="typing-utilities"></a> | ||
### Typing utilities | ||
Ferrum provides utilities for working with types that can be safely | ||
used with null and undefined. | ||
```js | ||
class {isdef, type, typename} = require('ferrum'); | ||
isdef(0); // => true | ||
isdef(null); // => false | ||
isdef(undefined); // => false | ||
type(22); // => Number | ||
type(null); // => null | ||
type(undefined); // => undefined | ||
typename(type(22)); // => "Number" | ||
typename(type(null)); // => "null" | ||
typename(type(undefined)); // => "undefined" | ||
``` | ||
The usual strategy of using `value.constructor` and `value.constructor.name` | ||
yields errors for `null` & `undefined`. | ||
<a name="functional-utilities"></a> | ||
### Functional Utilities | ||
```js | ||
const {curry, pipe, filter, isdef, uniq, map, plus} = require('ferrum'); | ||
// Using pipe() + auto currying instead of chaining | ||
pipe( | ||
[0,1,2,null,3,4,null,5,1,3,2,null,1,4], | ||
filter(isdef), // Filter out every null & undefined | ||
uniq, // Remove duplicates | ||
map(plus(2))); // Add two to each element | ||
// => [2,3,4,5,6,7] | ||
// Auto currying | ||
const pair = curry('pair', (a, b) => [a, b]); | ||
pair(1,2); // => [1,2] | ||
pair(2)(1); // => [1,2] | ||
``` | ||
<a name="development"></a> | ||
## Development | ||
<a name="build"></a> | ||
### Build | ||
```bash | ||
$ npm install | ||
``` | ||
<a name="test"></a> | ||
### Test | ||
```bash | ||
$ npm test | ||
``` | ||
<a name="lint"></a> | ||
### Lint | ||
```bash | ||
$ npm run lint | ||
``` |
@@ -18,2 +18,4 @@ /* | ||
const assert = require('assert'); | ||
const { inspect } = require('util'); | ||
const { curry, pipe } = require('./functional'); | ||
@@ -122,3 +124,3 @@ const { | ||
* | ||
* ```` | ||
* ``` | ||
* > [1,2,3,4].forEach(console.log) | ||
@@ -702,2 +704,29 @@ * 1 0 [ 1, 2, 3, 4 ] | ||
/** | ||
* Assert that two finite sequences are equals. | ||
* @function | ||
* @param {Sequence} a Any sequence for which iter() is defined | ||
* @param {Sequence} b Any sequence for which iter() is defined | ||
* @param {String|undefined} msg The error message to print | ||
* @throws {AssertionError} | ||
* @returns {Boolean} | ||
*/ | ||
const assertSequenceEquals = (a, b, msg) => { | ||
const P = v => inspect(v, { | ||
depth: null, breakLength: 1, compact: false, sorted: true, | ||
}); | ||
a = list(a); | ||
b = list(b); | ||
if (!eq(a, b)) { | ||
throw new assert.AssertionError({ | ||
message: `The sequences are not equal${msg ? `: ${msg}` : '!'}`, | ||
actual: P(a), | ||
expected: P(b), | ||
operator: 'seqEq()', | ||
stackStartFn: assertSequenceEquals, | ||
}); | ||
} | ||
}; | ||
/** | ||
* Determine the number of elements in an iterator. | ||
@@ -1437,2 +1466,3 @@ * This will try using trySize(), but fall back to iterating | ||
seqEq, | ||
assertSequenceEquals, | ||
each, | ||
@@ -1439,0 +1469,0 @@ find, |
@@ -351,2 +351,5 @@ /* | ||
// Ensure that eq(NaN, NaN) yields true | ||
Equals.impl(Number, (a, b) => a === b || (Number.isNaN(a) && Number.isNaN(b))); | ||
// Compare as string | ||
@@ -353,0 +356,0 @@ [Date, ..._maybeURL].map((Typ) => { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
140276
13
3410
0
600
2
26
+ Addednpm-check-updates@^3.1.12
+ Added@sindresorhus/is@0.14.0(transitive)
+ Added@szmarczak/http-timer@1.1.2(transitive)
+ AddedJSONStream@1.3.5(transitive)
+ Addedagent-base@4.2.14.3.0(transitive)
+ Addedagentkeepalive@3.5.3(transitive)
+ Addedansi-align@3.0.1(transitive)
+ Addedansi-regex@2.1.13.0.14.1.15.0.1(transitive)
+ Addedansi-styles@2.2.13.2.1(transitive)
+ Addedaproba@1.2.0(transitive)
+ Addedargparse@1.0.10(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbluebird@3.7.2(transitive)
+ Addedboxen@3.2.0(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedbuffer-from@1.1.2(transitive)
+ Addedbuiltins@1.0.3(transitive)
+ Addedcacache@12.0.4(transitive)
+ Addedcacheable-request@6.1.0(transitive)
+ Addedcamelcase@5.3.1(transitive)
+ Addedchalk@1.1.32.4.2(transitive)
+ Addedchownr@1.1.4(transitive)
+ Addedci-info@2.0.0(transitive)
+ Addedcint@8.2.1(transitive)
+ Addedcli-boxes@2.2.1(transitive)
+ Addedcli-table@0.3.11(transitive)
+ Addedclone-response@1.0.3(transitive)
+ Addedcolor-convert@1.9.3(transitive)
+ Addedcolor-name@1.1.3(transitive)
+ Addedcolors@1.0.3(transitive)
+ Addedcommander@3.0.2(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedconcat-stream@1.6.2(transitive)
+ Addedconfigstore@4.0.0(transitive)
+ Addedcopy-concurrently@1.0.5(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ Addedcross-spawn@5.1.0(transitive)
+ Addedcrypto-random-string@1.0.0(transitive)
+ Addedcyclist@1.0.2(transitive)
+ Addeddebug@3.1.04.4.0(transitive)
+ Addeddecompress-response@3.3.0(transitive)
+ Addeddeep-extend@0.6.0(transitive)
+ Addeddefer-to-connect@1.1.3(transitive)
+ Addeddot-prop@4.2.1(transitive)
+ Addedduplexer3@0.1.5(transitive)
+ Addedduplexify@3.7.1(transitive)
+ Addedemoji-regex@7.0.38.0.0(transitive)
+ Addedencoding@0.1.13(transitive)
+ Addedend-of-stream@1.4.4(transitive)
+ Addederr-code@1.1.2(transitive)
+ Addedes6-promise@4.2.8(transitive)
+ Addedes6-promisify@5.0.0(transitive)
+ Addedescape-string-regexp@1.0.5(transitive)
+ Addedesprima@4.0.1(transitive)
+ Addedexeca@0.7.0(transitive)
+ Addedfast-diff@1.3.0(transitive)
+ Addedfiggy-pudding@3.5.2(transitive)
+ Addedfind-up@3.0.04.1.0(transitive)
+ Addedflush-write-stream@1.1.1(transitive)
+ Addedfrom2@2.3.0(transitive)
+ Addedfs-minipass@1.2.7(transitive)
+ Addedfs-write-stream-atomic@1.0.10(transitive)
+ Addedfs.realpath@1.0.0(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedgenfun@5.0.0(transitive)
+ Addedget-stdin@7.0.0(transitive)
+ Addedget-stream@3.0.04.1.05.2.0(transitive)
+ Addedglob@7.2.3(transitive)
+ Addedglobal-dirs@0.1.1(transitive)
+ Addedgot@9.6.0(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedhas-ansi@2.0.0(transitive)
+ Addedhas-flag@3.0.0(transitive)
+ Addedhas-yarn@2.1.0(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedhosted-git-info@2.8.9(transitive)
+ Addedhttp-cache-semantics@3.8.14.1.1(transitive)
+ Addedhttp-proxy-agent@2.1.0(transitive)
+ Addedhttps-proxy-agent@2.2.4(transitive)
+ Addedhumanize-ms@1.2.1(transitive)
+ Addediconv-lite@0.6.3(transitive)
+ Addediferr@0.1.5(transitive)
+ Addedignore-walk@3.0.4(transitive)
+ Addedimport-lazy@2.1.0(transitive)
+ Addedimurmurhash@0.1.4(transitive)
+ Addedinfer-owner@1.0.4(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedini@1.3.8(transitive)
+ Addedip@1.1.5(transitive)
+ Addedis-ci@2.0.0(transitive)
+ Addedis-core-module@2.16.0(transitive)
+ Addedis-fullwidth-code-point@2.0.03.0.0(transitive)
+ Addedis-installed-globally@0.1.0(transitive)
+ Addedis-npm@3.0.0(transitive)
+ Addedis-obj@1.0.1(transitive)
+ Addedis-path-inside@1.0.1(transitive)
+ Addedis-stream@1.1.0(transitive)
+ Addedis-yarn-global@0.3.0(transitive)
+ Addedisarray@1.0.0(transitive)
+ Addedisexe@2.0.0(transitive)
+ Addedjju@1.4.0(transitive)
+ Addedjs-yaml@3.14.1(transitive)
+ Addedjson-buffer@3.0.0(transitive)
+ Addedjson-parse-better-errors@1.0.2(transitive)
+ Addedjson-parse-helpfulerror@1.0.3(transitive)
+ Addedjson5@2.2.3(transitive)
+ Addedjsonparse@1.3.1(transitive)
+ Addedkeyv@3.1.0(transitive)
+ Addedkleur@3.0.3(transitive)
+ Addedlatest-version@5.1.0(transitive)
+ Addedlibnpmconfig@1.2.1(transitive)
+ Addedlocate-path@3.0.05.0.0(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedlowercase-keys@1.0.12.0.0(transitive)
+ Addedlru-cache@4.1.55.1.1(transitive)
+ Addedmake-dir@1.3.0(transitive)
+ Addedmake-fetch-happen@5.0.2(transitive)
+ Addedmimic-response@1.0.1(transitive)
+ Addedminimatch@3.1.2(transitive)
+ Addedminimist@1.2.8(transitive)
+ Addedminipass@2.9.0(transitive)
+ Addedminizlib@1.3.3(transitive)
+ Addedmississippi@3.0.0(transitive)
+ Addedmkdirp@0.5.6(transitive)
+ Addedmove-concurrently@1.0.1(transitive)
+ Addedms@2.0.02.1.3(transitive)
+ Addednested-error-stacks@2.0.1(transitive)
+ Addednode-alias@1.0.4(transitive)
+ Addednode-fetch-npm@2.0.4(transitive)
+ Addednormalize-package-data@2.5.0(transitive)
+ Addednormalize-url@4.5.1(transitive)
+ Addednpm-bundled@1.1.2(transitive)
+ Addednpm-check-updates@3.2.2(transitive)
+ Addednpm-normalize-package-bin@1.0.1(transitive)
+ Addednpm-package-arg@6.1.1(transitive)
+ Addednpm-packlist@1.4.8(transitive)
+ Addednpm-pick-manifest@3.0.2(transitive)
+ Addednpm-registry-fetch@4.0.7(transitive)
+ Addednpm-run-path@2.0.2(transitive)
+ Addedobject-assign@4.1.1(transitive)
+ Addedobject-keys@1.1.1(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedos-homedir@1.0.2(transitive)
+ Addedos-tmpdir@1.0.2(transitive)
+ Addedosenv@0.1.5(transitive)
+ Addedp-cancelable@1.1.0(transitive)
+ Addedp-finally@1.0.0(transitive)
+ Addedp-limit@2.3.0(transitive)
+ Addedp-locate@3.0.04.1.0(transitive)
+ Addedp-try@2.2.0(transitive)
+ Addedpackage-json@6.5.0(transitive)
+ Addedpacote@9.5.12(transitive)
+ Addedparallel-transform@1.2.0(transitive)
+ Addedpath-exists@3.0.04.0.0(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedpath-is-inside@1.0.2(transitive)
+ Addedpath-key@2.0.1(transitive)
+ Addedpath-parse@1.0.7(transitive)
+ Addedpify@3.0.0(transitive)
+ Addedprepend-http@2.0.0(transitive)
+ Addedprocess-nextick-args@2.0.1(transitive)
+ Addedprogress@2.0.3(transitive)
+ Addedpromise-inflight@1.0.1(transitive)
+ Addedpromise-retry@1.1.1(transitive)
+ Addedprompts@2.4.2(transitive)
+ Addedprotoduck@5.0.1(transitive)
+ Addedpseudomap@1.0.2(transitive)
+ Addedpump@2.0.13.0.2(transitive)
+ Addedpumpify@1.5.1(transitive)
+ Addedrc@1.2.8(transitive)
+ Addedrc-config-loader@2.0.5(transitive)
+ Addedreadable-stream@2.3.8(transitive)
+ Addedregistry-auth-token@4.2.2(transitive)
+ Addedregistry-url@5.1.0(transitive)
+ Addedrequire-from-string@2.0.2(transitive)
+ Addedrequireg@0.2.2(transitive)
+ Addedresolve@1.22.91.7.1(transitive)
+ Addedresponselike@1.0.2(transitive)
+ Addedretry@0.10.1(transitive)
+ Addedrimraf@2.7.1(transitive)
+ Addedrun-queue@1.0.3(transitive)
+ Addedsafe-buffer@5.1.25.2.1(transitive)
+ Addedsafer-buffer@2.1.2(transitive)
+ Addedsemver@5.7.26.3.1(transitive)
+ Addedsemver-diff@2.1.0(transitive)
+ Addedsemver-utils@1.1.4(transitive)
+ Addedshebang-command@1.2.0(transitive)
+ Addedshebang-regex@1.0.0(transitive)
+ Addedsignal-exit@3.0.7(transitive)
+ Addedsisteransi@1.0.5(transitive)
+ Addedsmart-buffer@4.2.0(transitive)
+ Addedsocks@2.3.3(transitive)
+ Addedsocks-proxy-agent@4.0.2(transitive)
+ Addedspawn-please@0.3.0(transitive)
+ Addedspdx-correct@3.2.0(transitive)
+ Addedspdx-exceptions@2.5.0(transitive)
+ Addedspdx-expression-parse@3.0.1(transitive)
+ Addedspdx-license-ids@3.0.20(transitive)
+ Addedsprintf-js@1.0.3(transitive)
+ Addedssri@6.0.2(transitive)
+ Addedstream-each@1.2.3(transitive)
+ Addedstream-shift@1.0.3(transitive)
+ Addedstring-width@2.1.13.1.04.2.3(transitive)
+ Addedstring_decoder@1.1.1(transitive)
+ Addedstrip-ansi@3.0.14.0.05.2.06.0.1(transitive)
+ Addedstrip-eof@1.0.0(transitive)
+ Addedstrip-json-comments@2.0.1(transitive)
+ Addedsupports-color@2.0.05.5.0(transitive)
+ Addedsupports-preserve-symlinks-flag@1.0.0(transitive)
+ Addedtar@4.4.19(transitive)
+ Addedterm-size@1.2.0(transitive)
+ Addedthrough@2.3.8(transitive)
+ Addedthrough2@2.0.5(transitive)
+ Addedto-readable-stream@1.0.0(transitive)
+ Addedtype-fest@0.3.1(transitive)
+ Addedtypedarray@0.0.6(transitive)
+ Addedunique-filename@1.1.1(transitive)
+ Addedunique-slug@2.0.2(transitive)
+ Addedunique-string@1.0.0(transitive)
+ Addedupdate-notifier@3.0.1(transitive)
+ Addedurl-parse-lax@3.0.0(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
+ Addedvalidate-npm-package-license@3.0.4(transitive)
+ Addedvalidate-npm-package-name@3.0.0(transitive)
+ Addedwhich@1.3.1(transitive)
+ Addedwidest-line@2.0.1(transitive)
+ Addedwrappy@1.0.2(transitive)
+ Addedwrite-file-atomic@2.4.3(transitive)
+ Addedxdg-basedir@3.0.0(transitive)
+ Addedxtend@4.0.2(transitive)
+ Addedy18n@4.0.3(transitive)
+ Addedyallist@2.1.23.1.1(transitive)
Updatedlodash.isplainobject@4.0.6