Home - csgrimes1/wave-collapse GitHub Wiki
About
ECMAScript has progressed from a sketchy language with strange variable scoping to a rich ecosystem that nurtures functional programming. Its untyped nature makes it easy to handle lists without generics, and I'm often surprised at how infrequently type conversion causes errors in my code.
A note on terminology: there are many terms I could use to describe collections of values. Some have precise mathematical meanings, such as set. Others, like collection are more generic. I'm going to use the simple term list to designate these collections of items. I'll use the term sequence to designate list-like semantics calculated on the fly.
The introduction of generators moved the language forward in capability. Generators are lazy sequences - that is, they do not emit a new element until a consumer asks for it. This is the analogy behind the name wave-collapse, since a quantum object does not collapse into a measurable state until an observer attempts to measure it. Generators in ECMAScript are questionably functional since they generally rely on mutability (see figure 1), but pragmatists may accept small, isolated use of mutation. In fact, this library does use mutation, hiding it away so that it is not necessary for the library's users to be anything but immutable.
function *gen () {
for (let i = 1; ; i++) {
yield i;
}
}
Figure 1: mutability in a generator
I gave consideration to writing an immutable generator pattern using pseudo-tail recursion. I thought this to be out of scope and worthy of its own library. This could be future work, or the ECMAScript standard may beat me to it!
Generators, once again, make lazy iteration possible as a language feature. However, many of the goodies that come along with lazy iteration are missing. The map
, reduce
, and filter
operations provide immutable transformations of Array
instances, but they are not supported at a lower level. This lower level I refer to is the Iterator
interface. The wave-collapse API implements a mixin around any Iterable
or Iterator
to support features present in other languages, such as flatten
, skip
, take
, and the previously mentioned transformer methods.
In addition to lack of transformer methods for iterators, ECMAScript also lacks any way to consume lists or sequences of Promise
s in a series pattern. The for(item of list)
and Array.from()
semantics operate in a series, but they are synchronous. That is, they step through each promise in a list without waiting for its fulfillment. The wave-collapse library introduces a familiar, monadic semantic to start consumption of lists and sequences, and it can consume serially on promises.
> waveCollapse.iterateOver([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)])
.map(x => 2 * x)
.reduce(waveCollapse.toArray)
.then(result => console.log('result:', result));
//console output:
> result: [ 2, 4, 6 ]
Figure 2: asynchronous iteration
The wave-collapse map
and filter
methods over an Iterator
provide a solution for list comprehension. The Scala language's for
semantic takes comprehensions a step further. It allows you to apply comprehension semantics over all combinations of two or more lists. The wave-collapse API provides a solution to that problem without the terse syntax employed by some functional languages. See figure 3 below for an example.
> waveCollapse.combinations(['a','b','c'])
.with([1,2,3,4])
.filter((letter,number) => number % 3 !==0)
.reduce(waveCollapse.toArray)
.then(result => console.log('result:', result));
//console output:
result: [ [ 'a', 1 ],
[ 'a', 2 ],
[ 'a', 4 ],
[ 'b', 1 ],
[ 'b', 2 ],
[ 'b', 4 ],
[ 'c', 1 ],
[ 'c', 2 ],
[ 'c', 4 ] ]
Figure 3: combinations
Notice the call to .filter(...)
. The arguments are spread to one per list, providing an intuitive way to predicate the output. It's also efficient, because early calls to filter will reduce the number of calls down the call chain, as some of the combinations will be eliminated.
Conclusion
This is the idea behind the API. It gives the developer semantics to iterate over and transform lists or lazy sequences. It can serialize asynchronous or synchronous lists without changing call syntax. It allows you to iterate over combinations similar to the Scala for
comprehension. Finally, it never imposes mutability on the user, as immutability is a favored best practice in functional programming.
I hope this library is more than a mathematical abstraction, and I look forward to seeing it applied to real world use cases!