The name is a reference to wave function collapse in quantum physics. At the quantum level, an wave/particle entity such as an electron exists in a state of superposition - all possible states - until it interacts with an observer. The observer's attempt to measure the entity cause it to collapse into a distinct state.
This is analogous to lazy iteration in software. The next item in a sequence can exist in an undefined state until the consumer of an iteration -- the observer -- wishes to seek its value.
ECMAScript has very limited support for lazy iteration. It supports generators, but you can only run map/reduce operations on arrays. This library lets you run map, filter, reduce, skip, and take on lazy iterators. Furthermore, it adds promise support to the normal map/reduce paradigm to make iteration asynchronous.
Cool. Lazy, asynchronous (or synchronous) iteration. Yawn (lazy yawn).
In addition to supporting lazy iteration, I wanted the library to be able to iterate over unions. Scala has list comprehension support allowing you to permute lists together in a union, and at each level of combination, you can filter with a predicate. The Scala syntax is a little rough on the eyes. This library supports the equivalent functionality in a simple, fluent syntax. It's not as terse, but in the spirit of ECMAScript, it should be readable and somewhat intuitive.
Why Do Series Iteration?
I mentioned that the API can iterate asynchronously over promisy iterables. As a careful observer, you may ask why that is even useful. You can iterate over iterables containing promises, and the act of iterating asynchrously, waiting on each to fullfill, seems like it may be a solution in search of a problem. However, there are a number of reasons such a solution could be useful:
- Throttling simultaneous asynchronous operations to one.
- Supporting iterables of promises other than array.
- Ability to invoke the first callback faster than Promise.all, since we do not wait on all promises to fulfill.
- Common semantic to handle promisy and atomic (or mixed) iterables.
npm install --save wave-collapse
The module exports (2) functions,
combination. It also exports
defaultApi, the purpose of which we will describe below.
Please see the examples for usage.
The API is built into composable, pluggable components. There are four types of components used in this library:
- Transformers are functions that handle the math of transforming iterable elements. By math, I mean that every call to map, filter, flatten, skip, etc. takes one element from the underlying iterable and transforms it to 0..n resulting elements. A map operation is 1:1. A filter operation is either 1:0 or 1:1. A flatten operation is 1:n. To further illustrate the mathiness of transformers, they operate through function composition and never cause an iteration to start. They're lazy: they wait for terminating functions called reducers to pull from the iterator.
- Reducers are terminating functions that start consuming from an iteration, then collect and summarize the results. Familiar reducers include sum and average, but often the developer will provide their own reducer callback.
- Iterables and Iterators are familiar from ECMAScript 6. This API makes use of this built-in functionality. However, the API goes a step further. It can iterate over promises serially, making asynchronous generator functions possible. The API also adds familiar methods to iterators - map, reduce, filter, take, skip, and flatten. Finally, iteration is lazy. Consumers pull from generators, and generators don't have to work any longer when consumption stops.
- Combination refers to combining each element with each element in one or more other sets. The feature works similar to the for comprehension in Scala, and it also supports filtering.
const waveCollapse = defaultApi;
iterator.map (transform) where transform: (value, index) => result
Puts the return value into the output iterator.
iterator.filter (predicate) where predicate (value, index) => result
If result is truthy, then
value is included in the output iterator. Otherwise,
it is excluded.
Emits the members of nested iterators in the output iterator.
Stops the output iterator when
number elements have been covered. This is useful
when iterating over an infinite generator.
iterator.takeWhile (predicate) where predicate (value, index) => result
Stops the output iterator when
predicate returns an untruthy result.
Hides the first
number elements of the iterator from the output iterator.
iterator.skipWhile (predicate) where predicate (value, index) => result
Hides elements from the output iterator until
predicate returns an untruthy result.
Then, all elements are returned.
iterator.reduce (reducer, initialValue) where reducer (accum, current, index) => nextAccum
Forces iteration to start. For each element of the underlying iterator, this function
is called, and it accumulates a result. There are three common reducers available through
Note that the reducer semantics are not sufficient to complete certain types of operations.
average requires a
sum followed by a post-processing step to divide the
sum by the number of elements. Therefore, if
reducer has a property named
it will be called as a function with the following signature:
defaultApi.iterateOver (target) where target is Iterator or Iterable
Returns an iterator in a lazy state. This iterator is not semantically equal to
Iterator, as it has a different interface. Rather, this iterator object
has transformer methods (like
filter) that compose with each other without
starting consumption of
target. Finally, when the user calls
reduce, this is when
iteration effectively starts.
combinator.with(other) where other is Iterator or Iterable
Combinator that is the union of the context
combinator.filter(predicate) where predicate: (a, b, c, ..., z) => result
a, b, c, etc. represent unique combinations of the lists in the context
Combinator. There will be one more parameter than the number of calls to
with in the
call chain. When the predicate result is falsy, the combination will be excluded from
defaultApi.combinations (target) where target is Iterator or Iterable
Combinator object that can create unions with other lists.
> const waveCollapse = require.defaultApi;>> waveCollapse.iterateOver.map.reduce.then;result: [ 2, 4, 6 ]CompletionMonad
Primitive value types like number, boolean, and string are "boxed"
when being treated like objects. When boxed, the primitive type
is an object with a
prototype.valueOf() method. This method allows it to be treated
as a primitive.
Applying this rule, the API's
CompletionMonad type can represent what
is, effectively, a synchronous promise with a primitive type's interface.
This class allows the API to treat everything in an iteration as if it was a promise, whether
or not it is asynchronous. As you may have noticed in the code above,
most iterations end in a call to
then(). However, for synchronous
valueOf() semantics make the call to