be-sequence v0.0.10-alpha
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:
- makes sequence operations generic,
- adds power to user types without inheriting from built-in classes, and
- 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 theSymbol.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
- any
- append
- enumerate
- every
- filter
- flatmap
- foldLeft
- foldRight
- head
- iota
- isIterable
- length
- map
- next
- partition
- range
- reduceLeft
- reduceRight
- ref
- remove
- repeat
- reverse
- sublist
- tabulate
- tail
- zip
Constructing
# enumerate(iter)
Return a sequence of Array
s. 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 Array
s. 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 Array
s. 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
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago