Fork me on GitHub

sloth.js

sloth.js

The lazy JavaScript iterator library.

sloth.js is a JavaScript library for working with lazy iterators, providing a way to create, compose and perform various other operations on them โ€” forming a composable algebra of operations on iterators.

sloth.js will be slower than conventional operations for short operations (though sometimes outperforms native implementations for some inexplicable reason), but where it shines is consuming large amounts of data along a pipeline, e.g. combining map, filter and foldl operations. This is because it doesn't allocate any space before actual iteration.

Inspired by Python's itertools module, Haskell's lazy list facilities and Jeremy Ashkenas's Underscore.js.

sloth.js is freely distributable under the terms of the MIT license.

sloth.ify sloth.ify(xs)

sloth.ify a sequence xs, returning an object usable with sloth.js operations. Slothification is an idempotent operation, meaning it can be used on a slothified variable multiple times without any issue.

Terminology

Lazy

Lazy operations are run as the sequence is iterated, rather than immediately when the function is called.

Strict

Strict operations run as soon as the function is called, allocating space for it immediately. You may wish to run some strict operations (e.g. reverse and sort) after forcing, as their in-place native equivalents will be faster.

Composable

Composable operations can have successive operations invoked on them, e.g. map().map().filter().nub().

Non-composable

Non-composable operations are found at the end of a sloth.ifyed chain, usually culminating in a result.

Maps, filters and folds

map map(f)

map applies a function f across all elements of a sequence.

This is a lazy, composable operation.

> sloth.ify([1, 2, 3]).
... map(function(x) { return x + 1; }).force();
[ 2, 3, 4 ]

filter filter(f)

filter selects elements from a sequence that are true when the predicate f is applied to them.

This is a lazy, composable operation.

> sloth.ify([1, 2, 3]).
... filter(function(x) { return x > 2; }).force();
[ 3 ]

foldl foldl(f, acc=this.next())

foldl is an implementation of the left-fold operation, also known as a left-reduce or inject.

This is a strict, non-composable operation.

> sloth.ify([1, 2, 3]).
... foldl(function(acc, x) { return acc + x; }, 1);
[ 7 ]

foldr foldr(f, acc=this.next())

foldr is an implementation of the right-fold operation, the reverse analog of the foldl operation.

This operation may be slow due the fact the entire list must first be reversed.

This is a strict, non-composable operation.

> sloth.ify([1, 2, 3]).
... foldr(function(x, acc) { return acc + x; }, 1);
[ 7 ]

Quantification

all all(f=sloth.id)

all checks if all values in the sequence are truthy or fulfill the predicate f (universal quantification).

This is a partially strict non-composable operation.

> sloth.ify([true, true, false]).all()
false

any any(f=sloth.id)

any checks if any values in the sequence are truthy or fulfill the predicate f (existential quantification).

This is a partially strict non-composable operation.

> sloth.ify([true, true, false]).any()
true

Maxima and minima

max max(f=sloth.cmp)

max returns the maximum value of the sequence using the comparison function f.

If you're looking for the maximum of an array, it is much more efficient to use Math.max.apply(Math, array) (up to 40x(!!) faster).

This is a strict, non-composable operation.

> sloth.ify([3, 4, 1]).max()
4

min min(f=sloth.cmp)

min returns the minimum value of the sequence using the comparison function f.

If you're looking for the minimum of an array, it is much more efficient to use Math.min.apply(Math, array) (up to 40x(!!) faster).

This is a strict, non-composable operation.

> sloth.ify([3, 4, 1]).min()
1

Taking and dropping

take take(n)

take yields a sequence with only the first n elements of the original sequence.

This is a lazy, composable operation.

> sloth.ify([1, 2, 3]).take(2).force();
[ 1, 2 ]

drop drop(n)

drop yields a sequence without the first n elements of the original sequence.

This is a lazy, composable operation.

> sloth.ify([1, 2, 3]).drop(2).force();
[ 3 ]

takeWhile takeWhile(f)

takeWhile yields a sequence with only the first contiguous sequence of elements that fulfill the predicate f.

This is a lazy, composable operation.

> sloth.ify([1, 2, 3, 1, 2, 3]).
... takeWhile(function(x) { return x < 3; }).force();
[ 1, 2 ]

dropWhile dropWhile(f)

dropWhile yields a sequence without the first contiguous sequence of elements that fulfill the predicate f.

This is a lazy, composable operation.

> sloth.ify([1, 2, 3, 1, 2, 3]).
... dropWhile(function(x) { return x < 3; }).force();
[ 3, 1, 2, 3 ]

Set operations

union union(ys, f=sloth.eq)

union yields a sequence with only the unique elements from this sequence and ys, using the given predicate f for equality.

This will drain the ys iterator.

This can be slow due to the use of nub.

This is a lazy, composable operation.

> sloth.ify([1, 2, 3, 3]).union([3, 4, 4, 5]).force();
[ 1, 2, 3, 4, 5 ]

intersect intersect(ys, f=sloth.eq)

intersect yields a sequence with only the unique elements present in this sequence and ys, using the given predicate f for equality.

This will drain the ys iterator.

Again, this can be slow due to the use of nub.

This is a semi-strict composable operation, as it requires the first iterator to be non-infinite.

> sloth.ify([1, 2, 3, 3]).intersect([3, 4, 4, 5]).force();
[ 3 ]

difference difference(ys, f=sloth.eq)

difference yields a sequence with the elements of sequence ys removed from this sequence, using the given predicate f for equality.

This will drain the ys iterator.

This does not return only unique elements, but the resulting sequence can be nub()ed.

This is a semi-strict composable operation, as it requires the second iterator to be non-infinite.

> sloth.ify([1, 2, 3, 3]).difference([3, 4, 4, 5]).force();
[ 1, 2 ]

symmetricDifference symmetricDifference(ys, f=sloth.eq)

symmetricDifference yields a sequence of the elements present in neither this sequence nor ys, using the given predicate f for equality.

This will drain the ys iterator.

This does not return only unique elements, but the resulting sequence can be nub()ed.

This is a strict, composable operation.

> sloth.ify([1, 2, 3, 3]).
... symmetricDifference([3, 4, 4, 5]).force();
[ 1, 2, 4, 4, 5 ]

Sequence utilities

each each(f)

each acts as a for-each loop and iterates through all elements of the sequence, applying the given function f to each. The current index is passed as the second parameter to f.

sloth.StopIteration can be thrown at any time to break out of the loop.

This is a strict, non-composable operation.

> sloth.ify([1, 2, 3, 3]).each(console.log);
1 0
2 1
3 2
3 3

concat concat(ys)

concat joins this sequence with ys, end-to-end.

This will drain the ys iterator.

This is a lazy, composable operation.

> sloth.ify([1, 2, 3]).concat([3, 4, 5]).force();
[1, 2, 3, 3, 4, 5]

product product(ys, ...)

product yields the Cartesian product of all the sequences passed in and this sequence, equivalent to a series of nested loops.

This will completely drain all iterators.

This is a lazy, composable operation.

> sloth.ify([1, 2]).product([3, 4]).force();
[ [ 1, 3 ], [ 1, 4 ], [ 2, 3 ], [ 2, 4 ] ]

cycle cycle()

cycle loops a list around itself infinitely.

This is a lazy, composable operation.

> sloth.ify([1, 2]).cycle().take(6).force();
[ 1, 2, 1, 2, 1, 2 ]

nub nub(f=sloth.eq)

nub removes duplicate elements from the sequence using the given predicate f for equality.

This is up to 10x slower than Underscore.js's uniq, but is more flexible in its operation.

This is a lazy, composable operation.

> sloth.ify([1, 2, 2, 3, 3]).nub().force();
[ 1, 2, 3 ]

enumerate enumerate()

enumerate takes each element and places it in an array with the index as the first element.

This is a lazy, composable operaton.

> sloth.ify([1, 2, 2, 3, 3]).enumerate().force();
[ [ 0, 1 ], [ 1, 2 ], [ 2, 2 ], [ 3, 3 ], [ 4, 3 ] ]

reverse reverse()

reverse yields the reverse iterator of this sequence.

This is a strict, composable operation.

> sloth.ify([1, 2, 2, 3, 3]).reverse().force();
[ 3, 3, 2, 2, 1 ]

sort sort(f=sloth.cmp)

sort sorts the sequence using the comparison function f.

This is a strict, composable operation.

> sloth.ify([1, 4, 3, 5, 2]).sort().force();
[ 1, 2, 3, 4, 5 ]

group group(f=sloth.eq)

group groups the sequence into subsequences using the predicate function f.

This is a lazy, composable operation.

> sloth.ify([1, 1, 2, 3, 4]).
... group().
... map(function(x) { return x.force() } ).force();
[ [ 1, 1 ], [ 2 ], [ 3 ], [ 4 ] ]

tee tee(n=2)

tee splits the sequence into n independent sequence iterators.

This may allocate a large amount of additional storage, and will exhibit unbounded growth on infinite sequences.

The original iterator should not be used.

This is a lazy, non-composable operation.

> iters = sloth.ify([1, 2, 3, 4, 5]).tee();
> iters[0].force();
[ 1, 2, 3, 4, 5 ]
> iters[1].force();
[ 1, 2, 3, 4, 5 ]

zip zip(ys, ...)

zip takes the sequences passed to it and yields a new sequence taking an element from each element and placing it into an array. The length of the resulting sequence is the length of the shortest sequence passed in.

This is somewhat more useful with JavaScript 1.7 destructuring assignment.

This is a lazy, composable operation.

> sloth.ify([1, 2, 3]).zip([2, 3, 4], [3, 4, 5]).force();
[ [ 1, 2, 3 ], [ 2, 3, 4 ], [ 3, 4, 5 ] ]

force force()

force gets all the elements from the iterator and places them into an array.

This is a strict, non-composable operation.

> sloth.ify([1, 2, 3]).force();
[ 1, 2, 3 ]

Advanced features

__iterator__ implements the iterator protocol for JavaScript 1.7.

Additional slothifications

range range(b) or range(a=0, b=Infinity, step=1)

Create a range. Comes in two variants:

Note that the end of the range does not include b โ€” this is influenced by Python's range function.

repeat repeat(x, n=Infinity)

Repeat an element n times.

Iterators

A lazy iterator in sloth.js is defined as a function (usually a closure) which can be repeatedly invoked to yield successive values of a sequence, until the appropriate exception, sloth.StopIteration, is thrown to indicate the end of the sequence.

iter iter(xs)

iter creates an low-level iterator for various common data types.

iterArray iterArray(array)

Create an iterator for an array. Note that this is the low-level iterator.

iterString iterString(string)

Create an iterator for a string. Note that this is the low-level iterator.

iterObject iterObject(string)

Create an iterator for an object which yields pairs of keys and values. This will immediately read in the object and generate a list of its keys and values and, as such, won't reflect any future changes to the object.

iterNextable iterNextable(nextable)

Create an iterator for an object with a .next() function (such as generators in JavaScript 1.7).

StopIteration StopIteration

StopIteration is an object which is thrown to indicate that the iterator has no more data left to yield, i.e. an end-of-stream situation.

A common example of this reaching the end of an array in a traditional for loop.

Some JavaScript engines (presently SpiderMonkey) support StopIteration exceptions.

StopIteration instances must not be instantiated --- it is a singleton exception object which is designed to be thrown as is.

Utility functions

id id(x)

A function where, given a value, returns the value (รก la Haskell).

cmp cmp(a, b)

The default comparison function, combining both lexicographic and numerical semantics.

eq eq(a, b)

The default equality function, which returns the strict equality of two values.