0.0.10-alpha • Published 7 years ago

be-sequence v0.0.10-alpha

Weekly downloads
3
License
MIT
Repository
github
Last release
7 years ago

sequence

A JavaScript library for basic operations on iterables.

Why sequence?

Operations on sequences should be simple. sequence expresses these operations in JavaScript. Use sequence because it:

  1. makes sequence operations generic,
  2. adds power to user types without inheriting from built-in classes, and
  3. doesn't impose any new syntax.

By default, JavaScript pivots iteration around objects:

[1, 2, 3].map(x => x * 2); // [2, 4, 6]

This is also true of Underscore:

_.map([1, 2, 3], x => x * 2); // [2, 4, 6]

...and will still be true of the proposed :: syntax:

[1, 2, 3]::map(x => x * 2);
// [1, 2, 3] is bound as `this` value of `map`

Here's the sequence alternative. It's nothing new — most function definitions are taken from Scheme implementations:

map((x, y) => x * y, [0, 1, 2], [3, 3, 3]); // iterator yielding 0, 3, 6

// or

[...map((x, y) => x * y, [0, 1, 2], [3, 3, 3])]; // Array [0, 3, 6]

This function signature gives us a couple of benefits:

  • The procedure to be called on the sequences is at the head of the parameter list. We can therefore apply Function.prototype.bind to create specific mapping functions without clever wrappings.
  • We can have any number of sequence arguments. "Map" is a generic concept to be applied. In this case, i-th member of each sequence is being passed to the procedure passed as the first parameter of map. This works for any practical number of sequence arguments.
  • The sequence objects are unaware of map. The only requirement is that they implement the Symbol.iterator() method.

sequence is about optimizing programmer productivity. It simplifies powerful operations on user types and increases expressive power.

General advice and constraints

sequence functions do not mutate their arguments. They do consume iterators. In general, an iterator should not have a lifecycle that lasts beyond the local scope in which it is used.

Where possible, functions return lazily evaluated iterators. If you're not used to this pattern, results may surprise you.

Fortunately, the ES6 spread operator makes things nicer:

const a = map(x => x + 2, iota(5)); // a is an iterator, will print as {}
const b = [...map(x => x * y, iota(5), repeat(3, 5)]; // b = [0, 3, 6, 9, 12]

Functions

Overview

Constructing

# enumerate(iter)

Return a sequence of Arrays. The i-th Array returned has two members: i and the i-th value of iter.

Array.from(enumerate(['a','b']))
[ [ 0, 'a' ], [ 1, 'b' ] ]

The behavior is the same as Python's enumerate() built-in.

# iota(count[, start = 0 , step = 1])

Return a generator of the sequence:

start, start + step, ..., start + ([count - 1] * step)

This sequence is always of length count.

# range(start[, stop , step])

Return a generator of the sequence:

start, start + step, ..., start + (n * step)

If step is positive, the next term is yielded while its value is less than stop. If step is negative, while its value is greater than stop.

NB: the function is not overloaded as in Python. for a length n sequence, use iota(n).

# repeat(value, k)

Return a sequence where every term is value.

If k is defined, the sequence has length k. Otherwise, it is infinite.

# tabulate(func, k)

Return a sequence where every term is the return value of func. Particularly useful if the return value isn't static.

If k is defined, the sequence has length k. Otherwise, it is infinite.

# zip(...iters)

Returns a sequence of Arrays. The elements of the i-th Array will be the i-th element of each iterator passed as input.

> Array.from(zip(['a', 'b'], [1, 2]))
[ [ 'a', 1 ], [ 'b', 2 ] ]

The behavior is effectively the same as Python's zip() built-in.

Cutting and pasting

# sublist(seq, start, stop)

Behavior is similar to Array.prototype.slice except that start and stop are both required.

# head(seq, k)

Same as sublist(0, k).

# tail(seq, k)

Yields all values of sequence excluding first k members.

# append(...seqs)

Return a sequence yielding the values of the 0th argument, followed by those of the 1st argument, and so on.

Filtering

# filter(predicate, seq)

Return a sequence yielding the members of seq for which predicate returns a truthy value.

# remove(predicate, seq)

Return a sequence yielding the members of seq for which predicate returns a falsy value.

# partition(predicate, seq)

Return an Array whose two members are also Arrays. The first Array holds all the members of seq for which predicate returns a truthy value. The second holds the remaining members.

The advantage of partition is that it only traverses seq once.

Mapping

# map(proc, ...seqs)

Yields in order the result of calling proc with arguments that are the i-th value of each of seqs.

> Array.from(map((x, y) => x * y, [1, 2, 3], [4, 5, 6]))
[ 4, 10, 18 ]

# flatmap(proc, ...seqs)

Yields a sequence appending the result of calling map on seqs.

Miscellaneous

# reverse(seq)

Returns an Array containing the members of seq in reverse order.

# isIterable(seq)

Returns true if seq implements a [Symbol.iterator]() method, otherwise false.

Reduction

# foldLeft(proc, init, seq)

Return the result of iteratively applying proc to the result of computation so far and the remaining members of seq

foldLeft((x, y) => x + y, 0, [1, 2, 3]);
// (((0 + 1) + 2) + 3)

foldLeft is guaranteed to use init as the first element in its computation.

# foldRight(proc, init, seq)

Return the result of recursively applying proc right-associatively to the result of computation so far and the remaining members of seq. The nature of right-associative operations on a sequence is recursive, and so is the implementation. Use it responsibly: a long list could exceed the allowed number of recursive calls.

In general, prefer foldLeft.

foldRight((x, y) => x + y, 0, [1, 2, 3]);
// (1 + (2 + (3 + 0)))

foldRight is guaranteed to use init as the first element in its computation.

# reduceLeft(proc, init, seq)

Return the result of iteratively applying proc to the result of computation so far and the remaining members of seq.

reduceLeft((x, y) => x + y, 0, [1, 2, 3]);
// ((1 + 2) + 3)

reduceLeft((x, y) => x + y, 0, []);
// 0

reduceLeft uses init only if the supply seq is empty.

# reduceRight(proc, init, seq)

Return the result of recursively applying proc right-associatively to the result of computation so far and the remaining members of seq. The nature of right-associative operations on a sequence is recursive, and so is the implementation. Use it responsibly: a long list could exceed the allowed number of recursive calls.

In general, prefer reduceLeft.

reduceRight((x, y) => x + y, 0, [1, 2, 3]);
// (1 + (2 + 3))

reduceRight((x, y) => x + y, 0, []);
// 0

reduceRight uses init only if the supply seq is empty.

# any(predicate, ...seqs)

Returns true if predicate evaluates true when called with the current member of each sequence passed. Returns as soon as predicate returns a true value. Therefore there is no guarantee that predicate will be called for all members of each seqs.

# every(predicate, ...seqs)

Returns true if predicate evaluates true when called with the each member of each sequence passed. Returns as soon as predicate returns a false value, or if it reaches the end of input. Therefore there is no guarantee that predicate will be called for all members of each seqs.

Select

# next(seq)

The next value of seq.

next([1, 2, 3]);
// 1

# length(seq, mustCount = false)

Returns the number of members in seq.

This is the only function that will "cheat" if it can. If seq is an instanceof Array and length in seq then seq.length will be returned.

If mustCount is true, length will always count members by iterating.

# ref(seq, k)

Returns the k-th member of seq.

License

MIT

0.0.10-alpha

7 years ago

0.0.9-alpha

7 years ago

0.0.8-alpha

7 years ago

0.0.7-alpha

7 years ago

0.0.6-alpha

7 years ago

0.0.5-alpha

7 years ago

0.0.4-alpha

7 years ago

0.0.3-alpha

7 years ago

0.0.2-alpha

7 years ago

0.0.1-alpha

7 years ago