Seq
Functional programming support (iterator methods) for ES6 generators #JavaScript
Install / Use
/learn @DanShappir/SeqREADME
Seq
Iteration methods for ES6 (JavaScript) Generators / Iterators
After trying out ES6 generators and iterators, it occurred to me that their designers are not big fans of functional programming. Otherwise how can you explain the total lack of iteration functions, and the significant reliance on various incarnations of the for loop. But JavaScript is a highly malleable Programming Language, so I decided to add them myself. The result is the Seq library that allows you to write code such as:
function* numbers() {
var n = 0;
while (true) {
yield n++;
}
}
numbers.map(v => 2 * v).head(10).forEach(v => console.log(v));
To list the first ten even numbers. Or alternatively:
numbers.filter(v => v % 2).head(10).forEach(console.log.bind(console));
Now isn't that nice! And it's all implemented in 250 lines of unminified JavaScript code!
As an interesting side-effect, the Seq library essentially eliminates the need for explicitly using iterators, since all the iteration methods are applied directly on the generators.
While Seq iteration methods look very much like the built-in Array.prototype methods, there is one very significant difference between them: all the Seq iteration methods are lazy evaluated. This means that only the values that are actually required are computed, on demand.
Installation and Usage
Simply use Bower:
- Install Bower: npm install -g bower
- Install the package: bower install sequences
- Referrence the file: bower_components/sequences/js/sequences.js
For testing purposes, you can simply add the following script URL to your HTML file: //rawgit.com/DanShappir/Seq/master/js/seq.js.
The library is implemented as a universal package that can be used with *CommonJS, as AMD (with RequireJS), or simply referenced via a <script> tag.
API
The majority of the services provides by the Seq library are accessed as iteration methods implemented on the generator prototype. This means the services are available as methods you can invoke directly on generator instances. Since most of these methods also return a generator instance, they can be chained together. In addition, several service functions are provided in the Seq function / namespace.
Seq(source)
This function accepts a single argument source, and transforms it into an appropriate generator. The following transformations rules are applied, in order:
- If the argument has an iterator, create a generator for the iterator (yield*)
- If the argument is already a generator, just return it
- If the argument is a function, create a generator that repeatdely invokes this function
- If the argument is a collection that doesn't have an iterator, create a generator that loops over the elements
- Otherwise create a simple generator that yields the provided argument
Using Seq enables you to apply the iteration methods on most any type of element, for example:
const sumNumbers = (...args) => {
return Seq(args)
.filter(arg => typeof arg === 'number')
.reduce((sum, arg) => sum + arg, 0);
};
console.log(sumNumbers(1, 2, 'hello', 3)); // outputs 6
or
Seq(document.head.children)
.filter(elem => elem.tagName === 'SCRIPT')
.forEach(script => console.log(script.url))
Seq.numbers([initialValue[, step])
Helper function that returns a generator that emits a sequence of numbers, by default 0, 1, 2, ... You can optionally specify a start value as the initialValue first argument. By default the start value is 0. You can also specify a step size. By default the step size is 1.
console.log(...Seq.numbers(2, 2).head(5)()); // outputs [2, 4, 6, 8, 10]
The generator returned by Seq.numbers can also receive an initial value and a step value. If specified, these values override any initial values provided to Seq.numbers itself.
Seq.toFilter(value)
Several of the API methods accept a filter argument. Often this will be a regular function (not a generator), in which case the specified filter works much the same way as callbacks provided to array iteration methods.
Seq.numbers().head(10).filter(v => v % 2).forEach(v => console.log(v));
In addition, filters that are not functions are also supporting, using the Seq.toFilter helper function. This function is used to synthesize filters from additional types, using the following process:
- If the argument is a regular function (not a generator) then use it as described above
- Otherwise use Seq to create a generator from the argument
- Create a filter function using that generator that tries to match its argument to all the generated values
For example:
g.until(['done', 'finished', 'end']).forEach(console.log.bind(console));
will display all the values provided by generator g, until g generates one of the strings 'done', 'finished', or 'end'.
Note: be careful when using a generator or an iterator as a filter. Since the value is compared to every item in the sequence until a match is found, the may cause an infinite loop if the sequence is not delimited.
Initialization
Generators can receive arguments, just like a regular function, and use these arguments to calculate the generated sequence, for example:
function* gen(init) {
while (true) {
yield v++;
}
}
var iter1 = gen(1); // 1, 2. 3, ...
var iter2 = gen(3); // 3, 4, 5, ...
The iteration methods allow passing initialization parameters as extra arguments, for example:
gen.head(42).forEach(v => console.log(v), 5); // 5, 6, 7, ...
5 is the initialization parameter that is ultimately passed to gen.
Since binding a generator returns a generator, the bind method can also be used to achieve the same effect:
gen.bind(null, 5).head(42).forEach(v => console.log(v)); // 5, 6, 7, ...
Iteration methods
isGenerator()
Polyfill for a method that is currently only available in Firefox. returns true invoked for a generator, and false otherwise. ```javascript function* gen() { yield 'hello'; } console.log(gen.isGenerator()); // true console.log((function () {}).isGenerator()); // false
### forEach(callback[, generatorInitialization...])
Invoke the specified callback function for every item in the sequence. The callback receives the current item as an argument. If the callback returns a value, that value is provided back to the generator as the result of the *yield* instruction. Any additional optional arguments will be provided as arguments to the generator.
```javascript
Seq.numbers().head(5).forEach(v => console.log(v), 2); // 2, 3, 4, 5, 6
Note: since forEach cannot stop the iteration, do not use it with undelimited generators. Instead use methods such as head and until to limit the sequence.
until(filter[, generatorInitialization...])
Creates a generator that emits all the provided values until the specified filter returns true (see filters section for details). If you pass arguments to the created generator, they will be passed on as-is to the original generator.
Seq.numbers().until(v => v > 3).forEach(v => console.log(v)); // 0, 1, 2, 3
asLongAs(filter[, generatorInitialization...])
Creates a generator that emits all the provided values as-long-as the specified filter returns true (see filters section for details). If you pass arguments to the created generator, they will be passed on as-is to the original generator.
head([length])
Creates a generator that emits the first length provided items. If length is omitted then 1 is used. If you pass arguments to the created generator, they will be passed on as-is to the original generator.
filter(filter)
Creates a generator that emits all the provided items for which the specified filter returns true (see filters section for details). If you pass arguments to the created generator, they will be passed on as-is to the original generator.
Seq.numbers().filter(v => v % 2 === 0).head(5).forEach(v => console.log(v)); // 0, 2, 4, 6, 8
exclude(filter)
Creates a generator that emits all the provided items for which the specified filter does not return true (see filters section for details). If you pass arguments to the created generator, they will be passed on as-is to the original generator.
skip(filter)
Creates a generator that emits all the provided items after the specified items are skipped. If the value provided as filter is a number then that number of items are skipped. Otherwise that value is used as a filter (see filters section for details). If you pass arguments to the created generator, they will be passed on as-is to the original generator.
Seq.numbers().skip(3).head(5).forEach(v => console.log(v)); // 3, 4, 5, 6, 7
map(callback)
Creates a generator that emits all the provided items as transformed by applying the callback function to them. The callback receives the current item as an argument. In addition to the current item, the callback receives as a second argument the value provided by the requesting iterator.
Seq.numbers().map(v => -v).head(5).forEach(v => console.log(v)); // 0, -1, -2, -3, -4
inverseMap(map)
Creates a generator that emits all the provided items as-is, but transforms values returned to the generator by applying the callback function to them. The callback receives the value to be returned to the generator as an argument. In addition, the callback receives as a second argument the original value provided by the source generator.
flatten([depth])
Converts a generator that also emits sequences (collections, generators and iterators), into a generator of simple values. The optional numeric depth ar
Related Skills
openhue
341.8kControl Philips Hue lights and scenes via the OpenHue CLI.
sag
341.8kElevenLabs text-to-speech with mac-style say UX.
weather
341.8kGet current weather and forecasts via wttr.in or Open-Meteo
tweakcc
1.5kCustomize Claude Code's system prompts, create custom toolsets, input pattern highlighters, themes/thinking verbs/spinners, customize input box & user message styling, support AGENTS.md, unlock private/unreleased features, and much more. Supports both native/npm installs on all platforms.
