xduce

The central module that brings all of the separate parts of the library together into a public API. Everything publicly available is available through this module or one of its child modules.

All of the functions in this module deal directly with transducers. But first, let's talk about the protocols that are going to be referred to throughout many of the function discussions.

Protocols

One of the key selling points for transducers is that the same transducer can be used on any type of collection. Rather than having to write a new map function (for example) for every kind of collection - one for an array, one for a string, one for an iterator, etc. - there is a single map transducer that will work with all of them, and potentially with any kind of collection. This is possible implementing protocols on the collections.

A protocol in JavaScript is much like an interface in languages like Java and C#. It is a commitment to providing a certain functionality under a certain name. ES2015 has seen the introduction of an iterator protocol, for example, and language support for it (the new for...of loop can work with any object that correctly implements the iterator protocol).

To support transduction, Xduce expects collections to implement four protocols.

  • iterator: a function that returns an iterator (this one is built in to ES6 JavaScript)
  • transducer/init: a function that returns a new, empty instance of the output collection
  • transducer/step: a function that takes an accumulator (the result of the reduction so far) and the next input value, and then returns the accumulator with the next input value added to it
  • transducer/result: a function that takes the reduced collection and returns the final output collection

iterator is the built-in JavaScript protocol. When called, it is expected to return an iterator over the implementing collection. This iterator is an object that has a next function. Each call to next is expected to return an object with value and done properties, which respectively hold the next value of the iterator and a boolean to indicate whether the iteration has reached its end. (This is a simplified explanation; see this MDN page for more detailed information.)

transducer/init (referred to from now on as init) should be a function that takes no parameters and returns a new, empty instance of the output collection. This is the function that defines how to create a new collection of the correct type.

transducer/step (referred to from now on as step) should be a function that takes two parameters. These parameters are the result of the reduction so far (and so is a collection of the output type) and the next value from the input collection. It must return the new reduction result, with the next value incorporated into it. This is the function that defines how reduce a value onto the collection.

transducer/result (referred to from now on as result) should be a function that takes one parameter, which is the fully reduced collection. It should return the final output collection. This affords a chance to make any last-minute adjustments to the reduced collection before returning it.

Arrays, strings, and objects are all given support for all of these protocols. Other collections will have to provide their own (though it should be noted that since iterator is built-in, many third-party collections will already implement this protocol). As an example, let's add transducer support to a third-party collection, the Immutable.List collection from immutable-js.

Immutable.List.prototype[protocols.init] = () => Immutable.List().asMutable();
Immutable.List.prototype[protocols.step] = (acc, input) => acc.push(input);
Immutable.List.prototype[protocols.result] = (value) => value.asImmutable();

Immutable.List already implements iterator, so we don't have to do it ourselves.

The init function returns an empty mutable list. This is important for immutable-js because its default lists are immutable, and immutable lists mean that a new list has to be created with every reduction step. It would work fine, but it's quite inefficient.

The step function adds the next value to the already-created list. Immutable.List provides a push function that works like an array's push, except that it returns the new list with the value pushed onto it. This is perfect for our step function.

The result function converts the now-finished mutable list into an immutable one, which is what's going to be expected if we're transducing something into an Immutable.List. In most cases, result doesn't have to do any work, but since we're creating an intermediate representation of our collection type here, this lets us create the collection that we actually want to output. (Without result, we would have to use immutable lists all the way through, creating a new one with each step function, since we wouldn't be able to make this converstion at the end.)

With those protocols implemented on the prototype, Immutable.List collections can now support any transduction we can offer.

Protocols

After talking a lot about protocols and showing how they're properties added to an object, it's probably pretty obvious that there's been no mention of what the actual names of those properties are. That's what protocols is for.

protocols means that the actual names aren't important, which is good because the name might vary depending on whether or not the JavaScript environment has symbols defined. That unknown quantity can be abstracted away by using the properties on the protocols object as property keys. (Besides, the actual name of the protocol will either be a Symbol for the name of the protocol or a string like '@@transducer/init', depending on whether Symbols are available, and those aren't a lot of fun to work with.)

The best way to use these keys can be seen in the immutable-js example above. Instead of worrying about the name of the key for the init protocol, the value of protocols.init is used.

protocols defines these protocol property names.

  • iterator: if this is built in (like in later versions of node.js or in ES2015), this will match the built-in protocol name.
  • init
  • step
  • result
  • reduced: used internally to mark a collection as already reduced
  • value: used internally to provide the actual value of a reduced collection

The final two values don't have a lot of use outside the library unless you're writing your own transducers.

How Objects Are Treated

Before getting onto the core functions, let's talk about objects.

Objects bear some thought because regularly, they aren't candidates for iteration. They don't have any inherent order, normally something that's necessary for true iteration, and they have two pieces of data (key and value) for every element instead of one. Yet it's undeniable that at least for most transformations, being able to apply them to objects would be quite handy.

For that reason, special support is provided end-to-end for objects.

Object iteration

Iterating over an object will produce one object per property of the original object. An order is imposed; by default, this order is "alphabetical by key". The iterator function can be passed a sorting function that can sort keys in any other way.

The result of the iteration, by default, is a set of objects of the form {k: key, v: value}, called kv-form. The reason for this form is that it's much easier to write transformation functions when you know the name of the key. In the regular single-property {key: value} form (which is still available by passing false as the third parameter to iterator), the name of the key is unknown; in kv-form, the names of the keys are k and v.

var obj = {c: 1, a: 2, b: 3};
var reverseSort = function (a, b) { return a < b ? 1 : b > a ? -1 : 0; };

var result = iterator(obj);
// asArray(result) = [{k: 'a', v: 2}, {k: 'b', v: 3}, {k: 'c', v: 1}]

result = iterator(obj, reverseSort);
// asArray(result) = [{k: 'c', v: 1}, {k: 'b', v: 3}, {k: 'a', v: 2}]

result = iterator(obj, null, false);
// asArray(result) = [{a: 2}, {b: 3}, {c: 1}]

result = iterator(obj, reverseSort, false);
// asArray(result) = [{c: 1}, {b: 3}, {a: 2}]

Internally, every object is iterated into kv-form, so if you wish to have it in single-property, you must use iterator in this way and pass that iterator into the transduction function.

Object transformation

The kv-form makes writing transformation functions a lot easier. For comparison, here's what a mapping function (for a map transformer) would look like if we were using the single-property form.

function doObjectSingle(obj) {
  var key = Object.keys(obj)[0];
  var result = {};
  result[key.toUpperCase()] = obj[key] + 1;
  return result;
}

Here's what the same function looks like using kv-form.

function doObjectKv(obj) {
  var result = {};
  result[obj.k.toUpperCase()]: obj.v + 1;
  return result;
}

This is easier, but we can do better. The built-in reducers also recognize kv-form, which means that we can have our mapping function produce kv-form objects as well.

function doObjectKvImproved(obj) {
  return {k: obj.k.toUpperCase(), v: obj.v + 1};
}

This is clearly the easiest to read and write - if you're using ES5. If you're using ES2015, destructuring and dynamic object keys allow you to write doObjectKv as

doObjectKv = ({k, v}) => {[k.toUpperCase()]: v + 1};

Reducing objects

The built-in reducers (for arrays, objects, strings, and iterators) understand kv-form and will reduce objects properly whether they're in single-property or kv-form. If you're adding transducer support for non-supported types, you will have to decide whether to support kv-form. It takes a little extra coding, while single-property form just works.

That's it for object-object reduction. Converting between objects and other types is another matter.

Every transducer function except for sequence is capable of turning an object into a different type of collection, turning a different type of collection into an object, or both. Objects are different because they're the only "collections" that have two different pieces of data per element. Because of this, we have to have a strategy on how to move from one to another.

Transducing an object into a different type is generally pretty easy. If an object is converted into an array, for instance, the array elements will each be single-property objects, one per property of the original object.

Strings are a different story, since encoding a single-property object to a string isn't possible (because every "element" of a string has to be a single character). Strings that are produced from objects will instead just be the object values, concatenated. Because objects are iterated in a particular order, this conversion will always produce the same string, but except in some very specific cases there really isn't a lot of use for this converstion.

var obj = {a: 1, b: 2};

var result = asArray(obj);
// result = [{a: 1}, {b: 2}]

result = asIterator(obj);
// result is an iterator with two values: {a: 1} and {b: 2}

result = into(Immutable.List(), obj)
// result is an immutable list with two elements: {a: 1} and {b: 2}

result = asString(obj);
// result is '12'

The opposite conversion depends on the values inside the collections. If those values are objects, then the result is an object with all of the objects combined (if more than one has the same key, the last one is the one that's kept). Otherwise, keys are created for each of the elements, starting with 0 and increasing from there.

This means that converting an object to any non-string collection and back produces the original object.

var result = asObject([{a: 1}, {b: 2}]);
// result = {a: 1, b: 2}

result = asObject([1, 2, 3]);
// result = {0: 1, 1: 2, 2: 3}

result = asObject('hello');
// result = {0: 'h', 1: 'e', 2: 'l', 3: 'l', 4: 'o'}
Source:

Namespaces

transducers
util

Members

(static, constant) protocols :module:xduce~protocolMap

Source:

The mapping of protocol names to their respective property key names.

The values of this map will depend on whether symbols are available, whatever is present here will be used as key names for protocol properties throughout the library.

Type:

Methods

(static) asArray(collection, xformopt) → {array}

Source:

Transforms the elements of the input collection and reduces them into a new array.

The transformer is optional. If it isn't present, this function just converts the input collection into an array.

const xform = map(x => x + 1);

let result = asArray([1, 2, 3, 4, 5], xform);
// result = [2, 3, 4, 5, 6]

result = asArray('12345', xform);
// result = [2, 3, 4, 5, 6]

result = asArray('12345');
// result = [1, 2, 3, 4, 5]

result = asArray({a: 1, b: 2});
// result = [{a: 1}, {b: 2}]
Parameters:
Name Type Attributes Description
collection *

The input collection. The only requirement of this collection is that it implement the iterator protocol. Special support is provided by the library for objects and pre-ES2015 arrays and strings (ES2015 arrays and strings already implement iterator), so any of those can also be used.

xform module:xduce~transducerFunction <optional>

A function that creates a transducer object that defines the transformation being done to the input collection's elements. Any of the transducers in this library can produce a suitable transducer function. If this isn't present, the input collection will simply be reduced into an array without transformation.

Returns:

An array containing all of the transformed values from the input collection elements.

Type
array

(static) asIterator(collection, xformopt) → {module:xduce~iterator}

Source:

Transforms the elements of the input collection and reduces them into a new iterator.

The transformer is optional. If it isn't present, this function just converts the input collection into an iterator.

(The results here are shown passed through asArray because there's no literal interpretation of an iterator to show. The asArray calls are for demonstration purposes only.)

const xform = map(x => x + 1);
function* five() {
  for (let i = 1; i <= 5; ++i) {
    yield i;
  }
};

let result = asIterator(five(), xform);
// asArray(result) = [2, 3, 4, 5, 6]

result = asIterator([1, 2, 3, 4, 5], xform);
// asArray(result) = [2, 3, 4, 5, 6]

result = asIterator([1, 2, 3, 4, 5]);
// asArray(result) = [1, 2, 3, 4, 5]

result = asIterator({a: 1, b: 2});
// asArray(result) = [{a: 1}, {b: 2}]
Parameters:
Name Type Attributes Description
collection *

The input collection. The only requirement of this collection is that it implement the iterator protocol. Special support is provided by the library for objects and pre-ES2015 arrays and strings (ES2015 arrays and strings already implement iterator), so any of those can also be used.

xform module:xduce~transducerFunction <optional>

A function that creates a transducer object that defines the transformation being done to the input collection's elements. Any of the transducers in this library can produce a suitable transducer function. If this isn't present, the input collection will simply be reduced into an iterator without transformation.

Returns:

An iterator containing all of the transformed values from the input collection elements.

Type
module:xduce~iterator

(static) asObject(collection, xformopt) → {object}

Source:

Transforms the elements of the input collection and reduces them into a new object.

The transformer is optional. If it isn't present, this function just converts the input collection into an object.

const xform = map(({ k, v }) => ({ [k]: v + 1 }));

let result = asObject({a: 1, b: 2}, xform);
// result = {a: 2, b: 3}

result = asObject([{a: 1}, {b: 2}], xform);
// result = {a: 2, b: 3}

result = asObject([1, 2, 3, 4, 5]);
// result = {0: 1, 1: 2, 2: 3, 3: 4, 4: 5}

result = asObject('hello');
// result = {0: 'h', 1: 'e', 2: 'l', 3: 'l', 4: 'o'}
Parameters:
Name Type Attributes Description
collection *

The input collection. The only requirement of this collection is that it implement the iterator protocol. Special support is provided by the library for objects and pre-ES2015 arrays and strings (ES2015 arrays and strings already implement iterator), so any of those can also be used.

xform module:xduce~tranducerFunction <optional>

A function that creates a transducer object that defines the transformation being done to the input collection's elements. Any of the transducers in this library can produce a suitable transducer function. If this isn't present, the input collection will simply be reduced into an object without transformation.

Returns:

An object containing all of the transformed values from the input collection elements.

Type
object

(static) asString(collection, xformopt) → {string}

Source:

Transforms the elements of the input collection and reduces them into a new string.

The transformer is optional. If it isn't present, this function just converts the input collection into an string.

const xform = map(x => x.toUpperCase());

let result = asString('hello', xform);
// result = 'HELLO'

result = asString(['h', 'e', 'l', 'l', 'o'], xform);
// result = 'HELLO'

result = asString([1, 2, 3, 4, 5]);
// result = '12345'

result = asString({a: 1, b: 2});
// result = '12'
Parameters:
Name Type Attributes Description
collection *

The input collection. The only requirement of this collection is that it implement the iterator protocol. Special support is provided by the library for objects and pre-ES2015 arrays and strings (ES2015 arrays and strings already implement iterator), so any of those can also be used.

xform module:xduce~transducerFunction <optional>

A function that creates a transducer object that defines the transformation being done to the input collection's elements. Any of the transducers in this library can produce a suitable transducer function. If this isn't present, the input collection will simply be reduced into a string without transformation.

Returns:

A string containing all of the transformed values from the input collection elements.

Type
string

(static) compose(…fns) → {module:xduce~transducerFunction}

Source:

Composes two or more transducer functions into a single transducer function.

Each function that takes a transducer function (sequence, into, etc.) is only capable of accepting one of them. If there is a need to have several transducers chained together, then use compose to create a transducer function that does what all of them do.

This operates only on transducer functions, not on transducers themselves. There is no option for a shortcut form on a composed transducer function. They must be passed to functions that operate on them (sequence and the like).

NOTE: In functional programming, a compose function is generally ordered so that the first-executed function is listed last. This is done in the opposite way, with the first transducer executing first, etc. This is more sensible for transducer functions.

const add1 = x => x + 1;
const odd = x => x % 2 !== 0;

const xform = compose(map(add1), filter(odd), take(3));

const result = sequence([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], xform);
// result = [3, 5, 7];
Parameters:
Name Type Attributes Description
fns module:xduce~transducerFunction <repeatable>

One or more function that each create a transducer object that defines the transformation being done to a collection's elements. Any of the transducers in this library can produce a suitable transducer function.

Returns:

A transducer function that produces a transducer object that performs all of the transformations of the objects produced by the input transducer functions.

Type
module:xduce~transducerFunction

(static) into(target, collection, xformopt) → {*}

Source:

Transforms the elements of the input collection and reduces them into the target collection.

This is much like transduce, except that instead of explicitly providing a reducer (and perhaps an initial collection), the target collection acts as a reducer itself. This requires that the collection implement the init, result, and step transducer protocol functions.

If no transducer function is provided, the input collection elements are reduced into the target collection without being transformed. This can be used to convert one kind of collection into another.

const xform = map(x => x + 1);

let result = into([], [1, 2, 3, 4, 5], xform);
// result = [2, 3, 4, 5, 6]

result = into('', [1, 2, 3, 4, 5], xform);
// result = '23456'

result = into([], '12345', xform);
// result = [2, 3, 4, 5, 6]

result = into('', '12345', xform);
// result = '23456'

These examples are exactly equivalent to the four examples under transduce, but using into instead.

Parameters:
Name Type Attributes Description
target *

The collection into which all of the transformed input collection elements will be reduced. This collection must implement the init, result, and step protocol functions from the transducer protocol. Special support is provided for arrays, strings, and objects, so they need not implement the protocol.

collection *

The input collection. The only requirement of this collection is that it implement the iterator protocol. Special support is provided by the library for objects and pre-ES2015 arrays and strings (ES2015 arrays and strings already implement iterator), so any of those can also be used.

xform module:xduce~transducerFunction <optional>

A function that creates a transducer object that defines the transformation being done to the input collection's elements. Any of the transducers in this library can produce a suitable transducer function. If this isn't present, the input collection will simply be reduced into the target collection without transformation.

Returns:

The target collection, with all of the tranformed input collection elements reduced onto it.

Type
*

(static) iterator(obj, sortopt, kvopt) → {module:xduce~iterator}

Source:

Creates an iterator over the provided collection.

For collections that are not objects, it's as simple as that. Pass in the collection, get an iterator over that collection.

const iter = iterator([1, 2, 3]);
iter.next().value === 1;     // true
iter.next().value === 2;     // true
iter.next().value === 3;     // true
iter.next().done === true;   // true

Objects get special support though, as noted in the section above on iterating over objects. Objects are iterated in alphabetical order by key, unless a second parameter is passed to iterator. This must be a function that takes two parameters (which will be object keys) and returns -1 if the first is less than the second, 1 if the second is less than the first, and 0 if they're equal.

Also, iterator by default iterates objects into key/value form. However, if a third parameter of true is passed, it will instead render the object in kv-form. This is the form used internally when a transducer is invoked.

The second and third parameters are ignored if the input collection is not an object.

const iter = iterator({ b: 2, a: 4 });
iter.next().value.a === 4;     // true
iter.next().value.b === 2;     // true
iter.next().done === true;     // true

const kvIter = iterator({ b: 2, a: 4 }, null, true);
const { k: k1, v: v1 } = kvIter.next().value;
k1 === 'a' && v1 === 4;        // true
const { k: k2, v: v2 } = kvIter.next().value;
k2 === 'b' && v2 === 2;        // true
iter.next().done === true;     // true

Note that if this function is passed an object that looks like an iterator (an object that has a next function), the object itself is returned. It is assumed that a function called next conforms to the iteration protocol.

If this function is provided a function as its first argument, an iterator is returned which runs that function one time for each call to next. That function is provided two arguments: the index of the call (starting at 0 for the first time it's called and increasing by 1 per invocation after that) and the return value of the previous call to the function (starting at undefined for the first run before the function is ever called). If the function ever returns undefined, the iterator will terminate and set the done property of its return value to true at that point.

Note that since the initial value of the second argument is undefined, using default arguments is an excellent way of providing the function an initial value.

const constIter = iterator(() => 6);  // Bert's favorite number
constIter.next().value === 6;   // true
constIter.next().value === 6;   // true;
// This will go on forever, as long as `next` keeps getting called

const indexIter = iterator(x => x * x);
indexIter.next().value === 0;   // true
indexIter.next().value === 1;   // true
indexIter.next().value === 4;   // true
indexIter.next().value === 9;   // true
// Again, this will go on forever, or until the numbers get to big JS to handle

// Using default value on `last` parameter for initial value
const lastIter = iterator((index, last = 1) => last * (index + 1));  // Factorial
lastIter.next().value === 1;    // true
lastIter.next().value === 2;    // true
lastIter.next().value === 6;    // true
lastIter.next().value === 24;   // true
// Again, forever, though factorials get big quickly

// This iterator will terminate when the function returns `undefined`
const stopIter = iterator(x => x < 2 ? x : undefined);
stopIter.next().value === 0;    // true
stopIter.next().value === 1;    // true
stopIter.next().done === true;  // true
Parameters:
Name Type Attributes Default Description
obj *

The value to be iterated over.

sort module:xduce~sort <optional>

A function used to determine the sorting of keys for an object iterator. It has no effect when iterating over anything that is not a plain object.

kv boolean <optional>
false

Whether an object should be iterated into kv-form. This is only relevant when iterating over an object; otherwise its value is ignored.

Returns:

An iterator over the provided value. If the value is not iterable (it's not an array, object, or string, and it doesn't have a protocol-defined iterator), null is returned.

Type
module:xduce~iterator

(static) reduce(collection, reducer, init) → {*}

Source:

Reduces the elements of the input collection through a reducer into an output collection.

This is the lowest-level of the transduction functions. In fact, this one is so low-level that it doesn't have a lot of use in normal operation. It's more useful for writing your own transformation functions.

reduce doesn't assume that there's even a transformation. It requires an initial collection and a reducer object that is matched to that initial collection. The reducer object must implement the step and result protocols, which instruct reduce on how to build up the collection. The reducer may implement a transformation as well, but all that's important here is that it can do the reduction.

The input collection need only implement iterator. It is not necessary for the input and output collections to be of the same type; as long as the input implements iterator and the reducer implements step and result appropriate to the type of the init collection, then any translation between collection types can occur.

The normal course of operation will be to call transduce instead, as that function makes it easy to combine transformations with reductions and can optionally figure out the initial collection itself.

Parameters:
Name Type Description
collection *

The input collection. The only requirement of this collection is that it implement the iterator protocol. Special support is provided by the library for objects and pre-ES2015 arrays and strings (ES2015 arrays and strings already implement iterator), so any of those can also be used.

reducer object

An object that implements the step and result protocols. This object must know how to produce an output collection through those protocol functions.

init *

a collection of the same type as the output collection. It need not be empty; if it is not, the existing elements are retained as the input collection is reduced into it.

Returns:

A new collection, consisting of the init collection with all of the elements of the collection collection reduced into it.

Type
*

(static) sequence(collection, xform) → {*}

Source:

Transforms the elements of the input collection and reduces them into a new collection of the same type.

This is the highest level of the three main transduction functions (sequence, into, and transduce). It creates a new collection of the same type as the input collection and reduces the transformed elements into it. Additionally, unlike into and transduce, this function is capable of producing an iterator (as long as the input is an iterator).

The input collection must not only implement the iterator protocol (as in the last two functions) but also the init, result, and step transducer protocols. Special support is provided for arrays, strings, objects, and iterators, so they need not implement any protocol.

The obvious limitation of this function is that the type of output collection cannot be chosen. Since it is always the same as the input collection, this function cannot be used to convert a collection into a different type.

const xform = map(x => x + 1);

let result = sequence([1, 2, 3, 4, 5], xform);
// result = [2, 3, 4, 5, 6]

result = sequence('12345', xform);
// result = '23456'

These examples are identical to two of the four examples from the asX functions. The other two examples are not possible with sequence because they have different input and output collection types.

Parameters:
Name Type Description
collection *

The input collection. This must implement the iterator, init, result, and step protocols. Special support is provided for arrays, strings, objects, and iterators, so they do not have to implement any protocols.

xform module:xduce~transducerFunction

A function that creates a transducer object that defines the transformation being done to the input collection's elements. Any of the transducers in this library can produce a suitable transducer function.

Returns:

A collection of the same type as the input collection, containing all of the transformed values from the input collection elements.

Type
*

(static) toFunction(xform, reducer) → {module:xduce~step}

Source:

Creates a reduction function from a transducer and a reducer.

This produces a function that's suitable for being passed into other libraries' reduce functions, such as JavaScript's Array.prototype.reduce or Lodash's _.reduce. It requires both a transformer and a reducer because reduction functions for those libraries must know how to do both. The reducer can be a standard reducer object like the ones sent totransduce or reduce, or it can be a plain function that takes two parameters and returns the result of reducing the second parameter into the first.

If there is no need for a transformation, then pass in the identity transducer.

Parameters:
Name Type Description
xform module:xduce~transducerObject

A transducer object whose step function will become the returned reduction function.

reducer module:xduce~step | module:xduce~transducerObject

A reducer that knows how to reduce values into an output collection. This can either be a reducing function or a transducer object whose step function knows how to perform this reduction.

Returns:

A function that handles both the transformation and the reduction of a value onto a target function.

Type
module:xduce~step

(static) toReducer(collection) → {object}

Source:

Creates a reducer object from a function or from a built-in reducible type (array, object, or string).

To create a reducer for arrays, objects, or strings, simply pass an empty version of that collection to this function (e.g., toReducer([])). These reducers support the kv-form for objects.

The notable use for this function though is to turn a reduction function into a reducer object. The function is a function oftwo parameters, an accumulator and a value, and returns the accumulator with the value in it. This is exactly the same kind of function that is passed to reduction functions like JavaScript's Array.prototype.reduce and Lodash's _.reduce.

Note in particular that the output of this reducer does not need to be a collection. It can be anything. While transducing normally involves transforming one collection into another, it need not be so. For example, here is a reducer that will result in summing of the collection values.

const { toReducer, reduce } = xduce;

const sumReducer = toReducer((acc, input) => acc + input);
const sum = reduce([1, 2, 3, 4, 5], sumReducer, 0);
// sum = 15

This can be combined with transducers as well, as in this calculation of the sum of the squares of the collection values.

const { toReducer, transduce } = xduce;
const { map } = xduce.transducers;

const sumReducer = toReducer((acc, input) => acc + input);
const sum = transduce([1, 2, 3, 4, 5], map(x => x * x), sumReducer, 0);
// sum = 55
Parameters:
Name Type Description
collection *

An iterable collection or a reducer function.

Returns:

An object containing protocol properties for init, step, and result. This object is suitable for use as a reducer object (one provided to reduce or transduce). If the provided collection is not iterable, all of the properties of this object will be null.

Type
object

(static) transduce(collection, xform, reducer, initopt) → {*}

Source:

Transforms the elements of the input collection and reduces them into an output collection.

This is the lowest-level of the transduction functions that is likely to see regular use. It does not assume anything about the reducer, as it asks for it to be passed explicitly. This means that any kind of collection can be produced, since the reducer is not tied to the input collection in any way.

transduce also will accept an initial value for the resulting collection as the optional last parameter. If this parameter isn't present, then the initial value is determined from the transducer init protocol property on the reducer. Note however that many reducers may not provide an initial value, and in those cases it will have to be passed as a parameter.

const xform = map(x => x + 1);

const arrayReducer = {
  [protocols.init]() { return []; },
  [protocols.result](x) { return x; },
  [protocols.step](acc, x) {
    acc.push(x);
    return acc;
  }
};

const stringReducer = {
  [protocols.init]() { return ''; },
  [protocols.result](x) { return x; },
  [protocols.step](acc, x) { return acc + x; }
};

let result = transduce([1, 2, 3, 4, 5], xform, arrayReducer);
// result = [2, 3, 4, 5, 6]

result = transduce([1, 2, 3, 4, 5], xform, stringReducer);
// result = '23456'

result = transduce('12345', xform, arrayReducer);
// result = [2, 3, 4, 5, 6]

result = transduce('12345', xform, stringReducer);
// result = '23456'

These examples illustrate a number of important concepts. First of all, the transducer function is independent of the type of the collection; the same transducer function is used no matter the type of input or output collections. Secondly, two reducers are defined. These are objects that conform to the transducer protocol (see the documentation on module:xduce.protocols) and that know how to produce the output collection of choice. In this case, the reducers know how to create new arrays and strings (the init protocol) and how to add elements to arrays and strings (the step protocol). Because these reducers do have init protocol properties, the transduce calls do not require explicit initial collections.

The final point is that transduce can accept any kind of iterable collection, and by passing in the proper reducer, it can produce any kind of output collection. The same transduce function and the same map transformer is used in all four examples, despite the input/output combination being different in all four.

The init collection doesn't have to be empty. If it isn't, the elements that are already in it are retained and the transformed input elements are reduced into the collection normally.

Of course, the examples are not really necessary - the same thing could be accomplished using into without having to create the reducers (strings and arrays passed to into as targets know how to reduce themselves already).

Parameters:
Name Type Attributes Description
collection *

The input collection. The only requirement of this collection is that it implement the iterator protocol. Special support is provided by the library for objects and pre-ES2015 arrays and strings (ES2015 arrays and strings already implement iterator), so any of those can also be used.

xform module:xduce~transducerFunction

A function that creates a transducer object that defines the transformation being done to the input collection's elements. Any of the transducers in this library can produce a suitable transducer function.

reducer object

An object that implements the transducer protocols (init is only required if the init parameter is not present). This object must know how to produce an output collection through its step and result protocol functions.

init * <optional>

aAcollection of the same type as the output collection. If this is not present, then the reducer's init protocol function is called instead to get the initial collection. If it is present and not empty, then the existing elements remain and the transformed input collection elements are added to it.

Returns:

A collection of a type determined by the passed reducer. The elements of this collection are the results from the transformer function being applied to each element of the input collection.

Type
*

Type Definitions

init() → {*}

Source:

Returns a new, blank, empty instance of the target collection.

Returns:

An initial instance of the target collection. This is usually, but is not required to be, an empty instance of the collection.``

Type
*

iterator

Source:
Properties:
Name Type Description
next module:xduce~next

A function that, when called, returns the next iterator value.

A generic iterator. This conforms to the iterator protocol in that it has a next function that produces iterator-compatible objects.

Type:
  • object

next() → {module:xduce~nextValue}

Source:

The function that makes an object an iterator. This can be called repeatedly, with each call returning one iterator value, in order. This function must therefore keep state to know which of the values is the one to return next, based on the values that have already been returned by prior calls.

Returns:

An object containing the status and value of the next step of the iteration.

Type
module:xduce~nextValue

nextValue

Source:
Properties:
Name Type Attributes Description
done boolean

A flag to indicate whether there are any more values remaining in the iterator. Once this becomes true, there are no more iterator values (and the object may not even have a value property).

value * <optional>

The value returned by the iterator on this step. As long as done is false, this will be a valid value. Once done returns true, if there will be no further valid values (the spec allows a "return value", but this library does not use that).

An object returned by an iterator's next function. It has two properties so one can be used as a flag, since values like false and undefined can be legitimate iterator values.

Type:
  • object

protocolMap

Source:
Properties:
Name Type Description
init string | Symbol

The iterator protocol. This is built-in in ES2015+ environments; in that case the built-in protocol will be the value of this property.

init string | Symbol

The transducer/init protocol. This is used to mark functions that initialize a target collection before adding items to it.

step string | Symbol

The transducer/step protocol. This is used to mark functions that are used in the transducer's step process, where objects are added to the target collection one at a time.

result string | Symbol

The transducer/result protocol. This is used to mark functions that take the final result of the step process and return the final form to be output. This is optional; if the transducer does not want to transform the final result, it should just return the result of its chained transducer's result function.

reduced string | Symbol

The transducer/reduced protocol. The presence of this key on an object indicates that its transformation has been completed. It is used internally to mark collections whose transformations conclude before every object is iterated over (as in xduce.take transducers.) It is of little use beyond transducer authoring.

value string | Symbol

The transducer/value protocol. This is used internally to mark properties that contain the value of a reduced transformation. It is of little use beyond transducer authoring.

The mapping of protocol names to their respective property key names. The values of this map will depend on whether symbols are available.

Type:
  • object

result(input) → {*}

Source:

Performs any post-processing on the completely reduced target collection. This lets a transducer make a final, whole-collection transformation, particularly useful when the step function has been used on an intermediate form of the collection which is not meant to be the output.

Parameters:
Name Type Description
input *

The final, reduced collection derived from using module:xduce~step on each of the original collection's elements.

Returns:

The final collection that is the result of the transduction.

Type
*

sort(a, b) → {number}

Source:

A function used for sorting a collection.

Parameters:
Name Type Description
a *

The first item to compare.

b *

The second item to compare.

Returns:

Either 1 if a is less than b, -1 if a is greater than b, or 0 if a is equal to b.

Type
number

step(acc, value) → {*}

Source:

Performs a transformation on a single element of a collection, adding it to the target collection at the end. Thus, this function performs both the transformation and the reduction steps.

Parameters:
Name Type Description
acc *

The target collection into which the transformed value will be reduced.

value *

A single element from the original collection, which is to be tranformed and reduced into the target collection.

Returns:

The resulting collection after the provided value is reduced into the target collection.

Type
*

transducer(collectionopt, …params) → {*|module:xduce~transducerFunction}

Source:

A function that is responsible for performing transductions on collections.

These functions have two forms. If no input collection is supplied, then this takes a set of configuration parameters and returns a transducer function configured to handle that specific transformation.

There is also a shortcut form, where an input collection is supplied. In this case, a transducer function is still configured and created, but then it is immediately applied as though sequence was called with that collection and transducer function. The transformed collection is then returned.

Parameters:
Name Type Attributes Description
collection * <optional>

An optional input collection that is to be transduced.

params * <repeatable>

Parameters that are used to configure the underlying transformation. Which parameters are necessary depends on the transducer. See the individual transducers for details.

Returns:

If a collection is supplied, then the function returns a new collection of the same type with all of the elements of the input collection transformed. If no collection is supplied, a transducer function, suitable for passing to sequence, into, etc. is returned.

Type
* | module:xduce~transducerFunction

transducerFunction(xform) → {module:xduce~transducerObject}

Source:

A function that creates a transducer object and links it to the next one in the chain.

Parameters:
Name Type Description
xform module:xduce~transducerObject

A transducer object to chain the new transducer object to.

Returns:

A new transducer object already chained to xform.

Type
module:xduce~transducerObject

transducerObject

Source:
Properties:
Name Type Description
@@transducer/init module:xduce~init

An implementation of the transducer init protocol. In environments where symbols are available, this will be named Symbol.for('transducer/init').

@@transducer/step module:xduce~step

An implementation of the transducer step protocol. In environments where symbols are available, this will be named Symbol.for('transducer/step').

@@transducer/result module:xduce~result

An implementation of the transducer result protocol. In environments where symbols are available, this will be named Symbol.for('transducer/result').

An object implementing all three transduction protocols (init, step, and result) which is used by the engine to define transduction.

Transducer objects can (and must) be chained together. For instance, none of the transducer functions defined in module:xduce.transducers produces objects that know how to reduce transformed values into an output collection. This is the entire point; reduction is separate from transformation, which allows transformations to be used no matter the type of the output collection. So the engine automatically chains transducer objects to a reducer object (which is basically a specialized transducer objects whose step function transforms its inputs by adding them to a collection) that does know how to create an output collection. Thus, the protocol methods need to call the protocol methods of the next transducer object in the chain.

For that reason, transducer objects are not created manually. They are instead created by transducer functions that automatically create transducer objects and link them to the next transducer object in the chain.

Type:
  • object