When utilizing array higher order functions, the entire process is eagerly completed at each step.
const isOdd = v => v % 2 == 1;
const multiply = by => v => v * by;
const arrRange = IntStream.range(10, 20);
const arrOdd = arrRange.filter(isOdd);
const arrOddM3 = arrOdd.map(multiply(3));
Each step creates distinct arrays as bindings. Even when chaining them together, intermediate arrays are always generated and the full array must be completed before moving on to the next step.
const arrOddM3 = IntStream.range(10, 20).filter(isOdd).map(multiply(3));
arrOddM3; // ==> [33, 39, 45, 51, 57]
Streams handle things differently as they only compute values when accessed. A stream implementation would have a similar appearance.
const streamOddM3 = Stream.range(10, Infinity).filter(isOdd).map(multiply(3));
streamOddM3; // ==> Stream
By changing the end point to infinity, calculations may start with just the very first value, or even none until requested. To force the computations, requesting values returns an array:
streamOddM3.take(3); // ==> [33, 39, 45]
The following code demonstrates a Stream implementation inspired by the one showcased in the SICP videos operating akin to Java's streams.
// Class implementations here...
Alternatives such as generators and transducers offer different solutions.
In JavaScript, generators (also known as coroutines) provide an option where you can create map and filter generator functions from a generator source to produce a new generator after transformation. Utilizing generators, already present in the language, may be a better fit compared to Streams but further exploration is required to create a generator example based on the above scenario.
Clojure introduces transducers, allowing composition of steps so that list creation only occurs for elements included in the final output. Easily implementable in JavaScript, transducers provide another approach to streamline operations.