@ricokahler/lazy
TypeScript icon, indicating that this package has built-in type declarations

2.0.4 • Public • Published

@ricokahler/lazy · codecov github status checks bundlejs.com semantic-release

A small (~900B gzip*), useful set of methods for lazy iteration of iterables.



Why this lazy lib?

  1. Small size (~900B gzipped)
  2. Modern for...of-only implementations. (Read the source here)
  3. Only ships useful* methods
  4. Support for async iterators

*Useful is a keyword here because this library only ships with iterable methods that can take advantage of chained lazy iteration.

What that boils down to is shipping methods that never have to exhaust the iterable in order to yield the next item.

An example of this would be a reverse() method. If this lib implemented a reverse() method, it would have to buffer the items received from the iterable into an array to be able to yield the items in reverse.

Since we have to buffer the items into an array anyway, there's not all that much difference between:

Lazy.from(iterable).reverse(); // 🛑 not implemented!

and

Lazy.from(iterable).to(Array).reverse();

This constraint has some mental model benefits too. If you find yourself frequently needing to convert your iterable to an array, then there's a good chance lazy evaluation is not giving you much benefit.

Do I even need a lazy lib?

In order to take full advantage of the short-circuiting nature of call-by-need/lazy-evaluation, your Lazy expression should either:

  1. terminate in short-circuiting method
  2. take (via take or takeWhile) a subset of the iterable

Installation

npm i @ricokahler/lazy

Usage

Both ESM and CJS are supported in this package.

// esm
import Lazy from '@ricokahler/lazy';
// common-js
const Lazy = require('@ricokahler/lazy');

You can then:

  1. chain the methods like arrays:
const result = Lazy.from([1, 2, 3])
  .map((x) => x + 1)
  .filter((x) => x >= 3)
  .first();

console.log(result); // 3
  1. Or use the static method counterparts:
let iterable = Lazy.from([1, 2, 3]);
iterable = Lazy.map(iterable, (x) => x + 1);
iterable = Lazy.filter(iterable, (x) => x >= 3);
const result = Lazy.first(iterable);

console.log(result); // 3

Method Reference

Conversion methods

Chainable methods

Short-circuiting, terminating methods

Conversion methods

Lazy.from | 🔝

Takes in any iterable and returns it wrapped in a Lazy with chainable Lazy methods.

// static method only
Lazy.from<T>(iterable: Iterable<T>): Lazy<T>

Note: this is equivalent to calling new Lazy(iterable)

to | 🔝

Writes the iterable into another data structure. Accepts an object with a from method that accepts an iterable (e.g. Array.from) or a constructor that accepts an iterable.

Note: if used with an async iterable, it will await and buffer all items into an array first.

The implementation is as follows:

function to(iterable, constructorOrFromable) {
  // switches behavior for async iterators
  if (typeof iterable[Symbol.asyncIterator] === 'function') {
    return toAsync(iterable, constructorOrFromable);
  }

  return toSync(iterable, constructorOrFromable);
}

async function toAsync(iterable, constructorOrFromable) {
  // Async iterators will be buffered into an array first
  const items = [];
  for await (const item of iterable) {
    items.push(item);
  }

  return toSync(items, constructorOrFromable);
}

function toSync(iterable, constructorOrFromable) {
  if (typeof constructorOrFromable.from === 'function') {
    return constructorOrFromable.from(iterable);
  }
  return new constructorOrFromable(iterable);
}
Lazy<T>.to<TInstance>(fromable: {from: (iterable: Iterable<T>) => TInstance}): TInstance
Lazy<T>.to<TInstance>(constructor: {new (iterable: Iterable<T>) => TInstance}): TInstance

Chainable methods

map | 🔝

Takes in an iterable and returns an iterable generator that yields the result of the callback function on each item from the input iterable.

Lazy<T>.map<R>(mapper: (t: T) => R): Lazy<R>

filter | 🔝

Takes in an iterable and returns an iterable generator that yields the accepted elements of the given callback function.

Lazy<T>.filter<R extends T>(accept: (t: T) => t is R): Lazy<R>
Lazy<T>.filter<R extends T>(accept: (t: T) => t is R): Lazy<R>

scan | 🔝

Takes in an iterable, a reducer, and an initial accumulator value and returns another iterable that yields every intermediate accumulator created in the reducer for each item in the input iterable.

Useful for encapsulating state over time.

Note: the initial accumulator value is required.

Lazy<T>.scan<TAcc>(reducer: (acc: TAcc, t: T) => TAcc, initialAcc: TAcc): Lazy<TAcc>

flat | 🔝

Returns a new iterable with all sub-iterable items yielded into it recursively up to the specified depth.

Lazy<T>.flat<TDepth extends number>(depth?: TDepth): Lazy<Flattened<T, TDepth>>

flatMap | 🔝

Calls the result of the given callback function on each item of the parent iterable. Then, yields the result of each into a flatted iterable. This is identical to a map followed by flat with depth 1.

Lazy<T>.flatMap<R>(mapper: (value: T) => R | Iterable<R>): Lazy<R>

take | 🔝

Yields the first n items of the given iterable and stops further iteration.

const result = Lazy.from([1, 2, 3, 4, 5]).take(3).to(Array);

console.log(result); // [1, 2, 3]
Lazy<T>.take(n: number): Lazy<T>

takeWhile | 🔝

Yields while the callback function accepts the current item from the given iterable. Iteration finishes as soon as an item is rejected by the callback.

const result = Lazy.from([1, 2, 3, 4, 5, 0, 1])
  .takeWhile((n) => n <= 2)
  .to(Array);
console.log(result); // [1, 2]
Lazy<T>.takeWhile<R extends T>(accept: (t: T) => t is R): Lazy<R>
Lazy<T>.takeWhile(accept: (t: T) => unknown): Lazy<T>

skip | 🔝

Skips over the first n items of the given iterable then yields the rest.

const result = Lazy.from([1, 2, 3, 4, 5]).skip(2).to(Array);
console.log(result); // [3, 4, 5]
Lazy<T>.skip(n: number): Lazy<T>

skipWhile | 🔝

Skips over the items while the given callback accepts the current item from the given iterable, then yields the rest.

const result = Lazy.from([1, 2, 3, 4, 5, 0, 1])
  .skipWhile((n) => n <= 2)
  .to(Array);

console.log(result); // [3, 4, 5, 0, 1]
Lazy<T>.skip(n: number): Lazy<T>

Short-circuiting, terminating methods

includes | 🔝

Determines whether an iterable includes a certain value using === comparison. Short-circuits iteration once the value is found.

Lazy<T>.includes(t: T): boolean
Lazy<T>.includes(t: T): Promise<boolean> // if async iterable is provided

first | 🔝

Returns the first item of an iterable or undefined if the iterable is done/exhausted.

Lazy<T>.first(): T | undefined
Lazy<T>.first(): Promise<T | undefined> // if async iterable is provided

find | 🔝

Returns the first item accepted by the given callback. Short-circuits iteration once an item is found.

Lazy<T>.find<R extends T>(accept: (t: T) => t is R): R | undefined
Lazy<T>.find(accept: (t: T) => unknown): T | undefined
Lazy<T>.find(accept: (t: T) => unknown): Promise<T | undefined> // if async iterable is provided

some | 🔝

Returns true if at least one item accepted by the given callback. Short-circuits iteration once an item is accepted.

Lazy<T>.some(accept: (t: T) => unknown): boolean
Lazy<T>.some(accept: (t: T) => unknown): Promise<boolean> // if async iterable is provided

every | 🔝

Returns true only if all items are accepted by the given callback. Short-circuits iteration once an item is rejected.

Lazy<T>.every(accept: (t: T) => unknown): boolean
Lazy<T>.every(accept: (t: T) => unknown): Promise<boolean> // if async iterable is provided

Package Sidebar

Install

npm i @ricokahler/lazy

Weekly Downloads

4

Version

2.0.4

License

MIT

Unpacked Size

329 kB

Total Files

18

Last publish

Collaborators

  • ricokahler