Iterable Sequence
A utility library for working with iterables in modern JavaScript and TypeScript.
Installation · Introduction · API
Motivation
With the ES2015 specification came the iteration protocol allowing us to write for..of
loops in JavaScript. However the built in language APIs do not provide some useful features that the use of the protocol enables.
The fundamental difference between working with iterables and working with arrays is that an iterable does not need an underlying data structure. This opens up the possibility for ranges, infinite Collections, data manipulation without copying entire data structures and many more.
Core features
- Lazy - values are computed only when they are actually used
- No mutation - functions and methods don't modify their arguments or internal state of their objects
- User friendly API - intuitive names and exhaustive documentation
- Types - the library was written in TypeScript and compiled with the
--strict
option
Installation
The library is published on npm. To use it in your own project run
npm install --save iterable-sequence
Then you can use the library like so:
// es2015 or TypeScript for of range5
// es5var seq = var values = seqconsole // outputs: 0, 1, 2, 3, 4
You can also use the provided script directly in the browser (using the following link is not recommended in production):
Introduction
Let's explore some of the basic capabilities of the library. Probably the most important function provided is range
.
Here is how you would use the range
function for iteration:
for of range5
If you know python this will surely look familiar. What's interesting is that like the newer versions of python the range
function doesn't create an array. Instead it creates an iterable object. This means that you can easily create infinite sequences:
for of rangeInfinity
range
is also versatile. You can specify the starting value and even the difference between consecutive values called step
.
Imagine however that it didn't have that capabilities. To achive the desired result we could use the map
function:
Sequences
range
, map
and most other functions of the library return a Sequence
. Objects of this class have methods corresponding to the standalone functions such as map
. Let's see this in action:
The Sequence
class is very powerful, because it can be created from many different data structures. You can use an iterable object (e.g. range(4)
, arrays: [1, 2, 3]
, strings: 'abc'
and many others), an array-like object (one that has numerical keys and a length property. e.g. { 0: 'a', 1: 'b', length: 2 }
) or even a generator function (this is what the library uses internally).
A Sequence
object can also be created using the constructor:
What else can you do with a Sequence
? How can you actually use the object? The main use case is iteration:
for of new Sequence'abc' new Sequence'abc' .forEachconsole.logx // outputs: 'a', 'b', 'c'
You can also convert a Sequence
to an Array or even a String. This is done using .toArray
and .join
:
console.logarray // outputs: ['a', 'b', 'c'] console.logstring // outputs: 'a-b-c'
Manipulation
So far we have only covered range
and map
. This is however only a small set of the functions that the library offers. Let's look at some others.
A very useful function is zip
. It can combine two collections? into a single sequence. Let's see it in action:
.ziprange6, 1, -1 // you can use destructuring on the valuesfor of zipped /* Output:first: 2, second: 6first: 3, second: 5first: 4, second: 3first: 5, second: 2*/
Sometimes you like your data so much you want even more of it. This is when repeat
and repeatValue
shine. Their purpose is what the name suggest. They are used to create sequences with values repeated over and over:
for of repeat, 3 for of repeatValue'a', 3
But sometimes you don't like your data that much. You would rather prefer to have less. Fortunately this library supports a family of filtering functions. Let's take a quick look at each of them:
Here we can see another aspect of the library. No function performs mutation. This is crutial, because we can reuse the Sequence
we have already created.
We have covered the most important features of the library. To see the list of all functions and read the detailed docs see the API section.
API
Index
Collection
Sequence
Sequence.toArray
Sequence.join
Sequence.forEach
range
repeat
repeatValue
zip
append
map
flatMap
filter
take
takeWhile
drop
dropWhile
reduce
Collection
The Collection
type is used all across the library. Objects of this type represent a collection of values, but they can represent it in different ways. Iterable
objects have a property @@iterator
that allows for iteration in a for..of
loop. ArrayLike
objects have numeric keys and a length
property. () => Iterator<T>
is a type that denotes a generator function (created using function*
).
The values of a collection can also be collections themselves.
Examples of Collection
objects:
Sequence
The Sequence
class is the main building block of the library. It encapsulates a Collection
object allowing for expressing data manipulations in the form of method chaining.
An Sequence
object is lazy. No values are computed unless you want to use them. This is because Sequence
uses generator functions internally. This opens up the possibility for infinite seqences as they don't store their values anywhere and compute them on demand.
Most Sequence
methods have their equivalents in the form of standalone functions of the same name.
To obtain an Sequence
just use the constructor like so:
Sequence.toArray
Return an array with the elements of this Sequence.
Example:
console.logvalues // outputs: ['a', 'b', 'c']
Sequence.join
Return a string formed by concatenating the string representation of the elements of this Sequence.
Arguments:
- separator: A string that will be used between the Sequence elements. Defaults to empty string.
Example:
console.logsequence.join // outputs: '123'console.logsequence.join'-' // outputs: '1-2-3'
Sequence.forEach
For each element of this Sequence call the supplied function with the value and index of this element.
Arguments:
- fn: The function to call with the values and indices of the elements of this Sequence.
Example:
sequence.forEachconsole.log /* outputs: 'x', 0 'y', 1 'z', 2*/
range
Return a Sequence of integers smaller than the value of the first parameter starting with the value of the second parameter. The value of the third parameter dictates the step.
Arguments:
- start: First element of the sequence. Defaults to 0.
- end: Upper limit of the sequence.
- step: Difference between two consecutive elements of the Sequence. Defaults to 1.
Example:
repeat
Return a Sequence whose elements are the elements of the passed collection repeated the specified number of times.
Arguments:
- collection: A Collection whose elements will be repeated in the resulting Sequence
- times: The number of times the elements are repeated. Defaults to Infinity
Example:
console.logtripleABC // outputs: 'abcabcabc' for of repeat
repeatValue
Return a Sequence consisting of the supplied value repeated the specified number of times.
Arguments:
- value: A value to repeat.
- times: The number of times the value is repeated. Defaults to Infinity.
Example:
for of repeatValue3, 5
zip
Return a Sequence whose elements are two element arrays created from the elements of the collections passed as arguments. The length of the sequence is equal to the length of the shorter collection.
Arguments:
- a: A Collection to zip
- b: A Collection to zip
Example:
console.logwithIndices // outputs: [['a', 0], ['b', 1], ['c', 2]] .zip'abc' .toArrayconsole.logwithIndicesReversed // outputs: [[0, 'a'], [1, 'b'], [2, 'c']]
append
Return a Sequence consisting of elements from the first collection followed by the elements from the second Arguments:
- first A Collection to use when forming the resulting sequence.
- second A Collection to use when forming the resulting sequence.
Example:
for of combined
map
Return a Sequence that contains the elements created from the input collection elements.
Arguments:
- collection: A collection to use as input.
- fn: A function that produces an element of the new Sequence using an element of the old collection.
Example:
.toArray console.loglettersDashNumbers // outputs: ['a-0', 'b-1', 'c-2']
flatMap
Return a Sequence that contains the elements of flattened collections created from the input collection elements.
Arguments:
- collection: A collection to use as input.
- fn: A function that produces an element of the new Sequence using an element of the old collection.
Example:
.flatMap .toArray console.logtimesThenPlus // outputs: [0, 3, 4, 5, 10, 7]
filter
Return a Sequence that contains the elements from the input collection that satisfy the predicate.
Arguments:
- collection: A collection to filter.
- predicate: A function that tests if a value satisfies some condition.
Example:
.filterx !== 4 .toArray console.lognoFours // outputs: [6, 5, 3]
take
Return a Sequence that contains the first elements of the collection. The argument specifies the number of elements to take. If the length of the collection is smaller, all of the colleciton elements will be present in the resulting sequence.
Arguments:
- collection: A collection to use as source of elements.
- count: The number of elements to take.
Example:
console.logfirstLetters // outputs: 'abcde'console.logfirstCats // outputs: 'Garfield and Puss and Smokey'
takeWhile
Return a Sequence that contains the elements from the input collection that occur before the element that no longer satisfies the predicate.
Arguments:
- collection: A collection to filter.
- predicate: A function that tests if a value satisfies some condition.
Example:
.takeWhilechar !== ' ' .join console.logfirstName // outputs: 'John'
drop
Return a Sequence that contains the elements of the collection without the first elements. The argument specifies the number of elements to omit.
Arguments:
- collection: A collection to use as source of elements.
- count: The number of elements to omit.
Example:
console.logallButFirst // outputs: 'yz'
dropWhile
Return a Sequence that contains the elements from the input collection that occur after the first element that satisfies the predicate including that element.
Arguments:
- collection: A collection to filter.
- predicate: A function that tests if a value satisfies some condition.
Example:
.repeat2 // 0, 1, 2, 3, 0, 1, 2, 3 .dropWhilex < 3 .toArray console.logresult // outputs: [3, 0, 1, 2, 3]
reduce
Apply a function against an accumulator and each element of the Collection to reduce it to a single value.
Arguments:
- collection A Collection whose elements will be reduces to a single value.
- fn A function that uses an accumulator and an element and reduces them to a single value.
Example:
console.logfactorial3 // outputs: 6