SkillAgentSearch skills...

Ferrum

Features from the rust language in javascript: Provides Traits/Type classes & a hashing infrastructure and an advanced library for working with sequences/iterators in js

Install / Use

/learn @adobe/Ferrum
About this skill

Quality Score

0/100

Category

Operations

Supported Platforms

Universal

README

<a name="ferrum"></a>

Ferrum

Features from the Rust language in JavaScript: Provides Traits & an advanced library for working with sequences/iterators in JS.

Github
API Documentation

<a name="table-of-contents"></a>

Table of Contents

<a name="status"></a>

Status

CircleCI codecov GitHub license GitHub issues LGTM Code Quality Grade: JavaScript semantic-release

<a name="usage-features"></a>

Usage & Features

$ npm add ferrum

<a name="hashing"></a>

Hashing & Hash Tables

Ferrum features an extensible, reliable infrastructure for object hashing including an implementation of HashMap and HashSet.

It supports user defined hash functions (e.g. blake2 instead of xxhash). Support for all of the standard types is provided out of the box and support for user defined types or third party types can be provided via the trait infrastructure.

You could even integrate the object-hash package to add support for hashing arbitrary third party types! See "Sophisticated hasher integrating object hash" in the hasher trait documentation.

const assert = require('assert');
const { HashMap } = require('ferrum');

const m = new Map([[{}, 42], [7, "seven"]]);
assert.strictEqual(m.get(7), "seven");
assert.strictEqual(m.get({}), undefined); // Identity based lookup

const hm = new HashMap([[{}, 42], [7, "seven"]]);
assert.strictEqual(hm.get(7), "seven");
assert.strictEqual(hm.get({}), 42); // Content based lookup

<a name="doctest"></a>

Testing of Examples

Have you ever found out that some of the examples in your api documentation or readme contained bugs? You can now use the Ferrum Doctest companion package to run your examples as part of your regular test harness!

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

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, 10, 12]);

In addition to supporting iterables & iterators, Ferrum/Sequence can take objects as input:

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] of iter(obj)) {
  log.push(`${key} | ${value}`);
}

assertEquals(log, [
  'foo | 23',
  'bar | 24',
]);

Ferrum/Sequence uses lodash.isPlainObject and always tries to use the iterator protocol to make sure object iteration is only used if it really should.

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

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:

const { map, plus, list, assertSequenceEquals } = require('ferrum');

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(map(a, list), [
  [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(map(b, list), [
  [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 never 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:

// 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]);

// This is much more handy
each([1,2,3], () => {
  ...
});

Underscore.js does not support currying at all; lodash provides curried 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.

const { sqrt, floor } = Math;
const {
  pipe, filter, uniq, map, mul, mapSort, identity, take,
  prepend, takeWhile, all, range, assertSequenceEquals,
  extend, plus, any,
} = 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(a, [3,9,15,21,33]);

// Very simple primality test
const isPrime = (v) => v > 1 && pipe(
  range(2, floor(sqrt(v)) + 1),
  map(x => v % x !== 0), // check that v is not divisible by x
  all);

// Sequence of all prime numbers (calculated slowly)
const primes = () => pipe(
  range(0, Infinity),
  filter(isPrime));

assertSequenceEquals(take(primes(), 5), [2, 3, 5, 7, 11]);

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 `

View on GitHub
GitHub Stars529
CategoryOperations
Updated24d ago
Forks26

Languages

JavaScript

Security Score

100/100

Audited on Mar 5, 2026

No findings