Skip to content

Latest commit

 

History

History
765 lines (470 loc) · 32.4 KB

iter_readme.md

File metadata and controls

765 lines (470 loc) · 32.4 KB

Overview

iter.mjs provides tiny utils for iteration and functional programming. Lightweight replacement for Lodash. Features:

  • Higher-order functions for data structures.
    • Common FP tools like map, filter, and many more.
    • Compatible with arbitrary iterables such as lists, sets, maps, dicts.

Differences from Lodash:

  • Supports arbitrary iterables and iterators, including sets and maps.
  • Much smaller and simpler.

Port and rework of https://github.com/mitranim/fpx.

TOC

Usage

import * as i from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/iter.mjs'

Perf

Carefully tuned for performance. Functions covered by benchmarks appear comparable to their native or Lodash equivalents. Many appear significantly faster.

JS performance is complicated and very unstable, Our benchmark suite is limited and checked only in V8. When in doubt, measure in your particular environment.

API

function arrOf

Links: source; test/example.

Signature: (seq<A>, test) => A[] where test: A => true.

Shortcut. Converts the input to an array via #arr and asserts that every element satisfies the given test function. Returns the resulting array.

function more

Links: source; test/example.

Takes an iterator, consumes one value, and returns true if the iterator is not yet finished. Shortcut for val.next().done === false.

function alloc

Links: source; test/example.

Shortcut for allocating an array with a sanity check. Same as Array(N) but ensures that the input is a natural_number suitable for array length. Avoids unintentionally passing any non-natural input such as Array(-1). Allows nil, replacing it with 0.

function arr

Links: source; test/example.

Converts an arbitrary sequence to an array. Supports the following inputs:

  • Nil: return [].
  • Array: return as-is.
  • List: convert via Array.prototype.slice.
  • Set or arbitrary iterator: convert to array by iterating.

Unlike #values, this function rejects other inputs such as non-nil primitives, dicts, maps, arbitrary iterables, ensuring that the input is always a sequence.

The input may or may not be a copy. To ensure copying, use #arrCopy.

function arrCopy

Links: source; test/example.

Similar to #arr, but always makes a copy, even if the input is already a true array.

function slice

Links: source; test/example.

Like Array.prototype.slice but allows arbitrary sequences compatible with #arr.

function keys

Links: source; test/example.

Takes an arbitrary input and returns an array of its keys:

  • For non-objects: always [].
  • For iterables with .keys(): equivalent to converting the output of .keys() to an array. Implementation varies for performance.
    • Examples: Array, Set, Map, and more.
  • For lists: equivalent to above for arrays.
  • For iterators: exhausts the iterator, returning an array of indexes equivalent to i.span(i.len(iterator)). See #span and #len.
  • For structs: equivalent to Object.keys.

function values

Links: source; test/example.

Takes an arbitrary input and returns an array of its values:

  • For non-objects: always [].
  • For arrays: returns as-is without copying.
  • For lists: slice to array.
  • For iterables with .values(): equivalent to converting the output of .values() to an array. Implementation varies for performance.
    • Examples: Set, Map, and more.
  • For iterators: equivalent to [...iterator].
  • For structs: equivalent to Object.values.

function valuesCopy

Links: source; test/example.

Variant of #values that always makes a copy. Mutating the output doesn't affect the original.

function entries

Links: source; test/example.

Takes an arbitrary input and returns an array of its entries (key-value tuples):

  • For non-objects: always [].
  • For iterables with .entries(): equivalent to converting the output of .entries() to an array. Implementation varies for performance.
    • Examples: Set, Map, and more.
  • For lists: equivalent to above for arrays.
  • For iterators: exhausts the iterator, returning an array of entries where keys are indexes starting with 0.
  • For structs: equivalent to Object.entries.

function reify

Links: source; test/example.

Takes an arbitrary value and attempts to deeply materialize it. Any iterators, or lists that contain iterators, or lists that contain lists that contain iterators, etc., are converted to arrays. Does not inspect other data structures such as sets or dicts.

function indexOf

Links: source; test/example.

Like Array.prototype.indexOf. Differences:

  • Uses is rather than ===, therefore able to detect NaN.
  • Input may be nil or any list.

function findIndex

Links: source; test/example.

Signature: (List<A>, A => bool) => int.

Like Array.prototype.findIndex. Differences:

  • Input may be nil or any list.
  • Doesn't support this or additional arguments.

function includes

Links: source; test/example.

Like Array.prototype.includes. Differences:

  • Supports arbitrary iterables compatible with #values.
  • Iterable may be nil, equivalent to [].

function append

Links: source; test/example.

Takes an arbitrary iterable compatible with #values and appends an arbitrary value, returning the resulting array.

function prepend

Links: source; test/example.

Takes an arbitrary iterable compatible with #values and prepends an arbitrary value, returning the resulting array.

function concat

Links: source; test/example.

Like Array.prototype.concat. Differences:

  • Takes two arguments, without rest/spread.
  • Supports arbitrary iterables compatible with #values.
  • Iterables may be nil, equivalent to [].

Note: for individual elements, use #append and #prepend.

function len

Links: source; test/example.

Universal length measurement:

  • For non-objects: always 0.
  • For iterables:
    • For lists: same as .length.
    • For ES2015 collections such as Set: same as .size.
    • For iterators: exhausts the iterator, returning element count.
  • For structs: equivalent to Object.keys(val).length.

function hasLen

Links: source; test/example.

Shortcut for #len > 0.

function each

Links: source; test/example.

Signature: (Iter<A>, A => void) => void.

Similar to Array.prototype.forEach, Set.prototype.forEach, Map.prototype.forEach, and so on. Differences:

  • Takes an arbitrary iterable compatible with #values.
  • Iterable may be nil, equivalent to [].
  • Doesn't support this or additional arguments.

function map

Links: source; test/example.

Signature: (Iter<A>, A => B) => B[].

Similar to Array.prototype.map. Differences:

  • Takes an arbitrary iterable compatible with #values.
  • Iterable may be nil, equivalent to [].
  • Doesn't support this, and doesn't pass additional arguments. When you want support for additional arguments, use #values to convert an arbitrary iterable to an array, then use native .map.

function mapMut

Links: source; test/example.

Similar to Array.prototype.map. Differences:

  • Mutates the input (which must be an array).
  • Doesn't support this or additional arguments.

For a non-mutating version, see #map.

function mapCls

Links: source; test/example.

Signature: (Iter<A>, {new(A): B}) => B[].

Similar to #map, but instead of taking an arbitrary function, takes a class and calls it with new for each element.

import * as i from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/iter.mjs'
import * as o from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/obj.mjs'

class Model extends o.Dict {pk() {return this.id}}
class Person extends Model {}

console.log(i.mapCls(
  [
    {id: 1, name: `Mira`},
    {id: 2, name: `Kara`},
  ],
  Person,
))

/*
[
  Person { id: 1, name: "Mira" },
  Person { id: 2, name: "Kara" },
]
*/

function mapCompact

Links: source; test/example.

Equivalent to i.compact(i.map(val, fun)). See #map and #compact.

function mapFlat

Links: source; test/example.

Signature: (Iter<A>, A => B[]) => B[].

Similar to Array.prototype.flatMap. Differences:

  • Takes an arbitrary iterable compatible with #values.
  • Iterable may be nil, equivalent to [].
  • Doesn't support this or additional arguments.

This function is equivalent to i.flat(i.map(val, fun)). See #map and #flat.

function filter

Links: source; test/example.

Signature: (Iter<A>, A => bool) => A[].

Similar to Array.prototype.filter. Differences:

  • Takes an arbitrary iterable compatible with #values.
  • Iterable may be nil, equivalent to [].
  • Doesn't support this or additional arguments.

function reject

Links: source; test/example.

Opposite of #filter. Equivalent to i.filter(val, l.not(fun)).

function compact

Links: source; test/example.

Equivalent to i.filter(val, l.id). Takes an arbitrary iterable and returns an array of its truthy #values, discarding falsy values.

function remove

Links: source; test/example.

Signature: (Iter<A>, A) => A[].

Takes an arbitrary iterable and an element to remove. Returns an array of the iterable's #values, discarding each occurrence of this element, comparing via is.

function fold

Links: source; test/example.

Signature: (src: Iter<A>, acc: B, fun: (B, A) => B) => B.

Similar to Array.prototype.reduce. Differences:

  • Takes an arbitrary iterable compatible with #values.
  • Iterable may be nil, equivalent to [].
  • Arguments are (src, acc, fun) rather than (fun, acc).
  • Accumulator argument is mandatory.
  • Doesn't support this.
  • Iterator function receives exactly two arguments: accumulator and next value.

function fold1

Links: source; test/example.

Signature: (src: Iter<A>, fun: (A, A) => A) => A.

Similar to #fold but instead of taking an accumulator argument, uses the first element of the iterable as the initial accumulator value. If the iterable is empty, returns undefined.

Similar to Array.prototype.reduce when invoked without an accumulator argument.

function find

Links: source; test/example.

Signature: (Iter<A>, A => bool) => A.

Similar to Array.prototype.find. Differences:

  • Takes an arbitrary iterable compatible with #values.
  • Iterable may be nil, equivalent to [].
  • Doesn't support this or additional arguments.

function procure

Links: source; test/example.

Signature: (src: Iter<A>, fun: A => B) => B.

Similar to #find, but returns the first truthy result of calling the iterator function, rather than the corresponding element. Equivalent to i.find(i.map(src, fun), l.id) but more efficient.

function every

Links: source; test/example.

Signature: (Iter<A>, A => bool) => bool.

Similar to Array.prototype.every. Differences:

  • Takes an arbitrary iterable compatible with #values.
  • Iterable may be nil, equivalent to [].
  • Doesn't support this or additional arguments.

function some

Links: source; test/example.

Signature: (Iter<A>, A => bool) => bool.

Similar to Array.prototype.some. Differences:

  • Takes an arbitrary iterable compatible with #values.
  • Iterable may be nil, equivalent to [].
  • Doesn't support this or additional arguments.

function flat

Links: source; test/example.

Similar to Array.prototype.flat. Differences:

  • Takes an arbitrary iterable compatible with #values.
  • Always flattens to infinite depth.

Currently flattens only children and descendants that are plain, preserving other nested iterables as-is.

function head

Links: source; test/example.

Takes an arbitrary iterable compatible with #values and returns its first element or undefined.

function last

Links: source; test/example.

Takes an arbitrary iterable compatible with #values and returns its last element or undefined.

function init

Links: source; test/example.

Short for "initial". Takes an arbitrary iterable compatible with #values and returns an array of all its values except last.

function tail

Links: source; test/example.

Takes an arbitrary iterable compatible with #values and returns an array of all its values except first.

function take

Links: source; test/example.

Takes an arbitrary iterable compatible with #values and returns N values from the start.

function count

Links: source; test/example.

Signature: (src: Iter<A>, fun: A => B) => nat.

Takes an arbitrary iterable compatible with #values, calls the given function for each value, and returns the count of truthy results. The count is between 0 and iterable length.

function compare

Links: source; test/example.

Signature: (a, b) => -1 | 0 | 1.

Equivalent to the default JS sort comparison algorithm. Sometimes useful for sorting via Array.prototype.sort or #sort, as a fallback.

function compareFin

Links: source; test/example.

Signature: (a, b) => -1 | 0 | 1 where arguments are nil or finite.

Sort comparison for finite numbers. Usable for Array.prototype.sort or #sort. Throws on non-nil, non-finite arguments.

function sort

Links: source; test/example.

Signature: (src: Iter<A>, fun?: (prev: A, next: A) => -1 | 0 | 1) => A[].

Similar to Array.prototype.sort. Differences:

  • Takes an arbitrary iterable compatible with #values.
  • Iterable may be nil, equivalent to [].
  • Always creates a new array. Does not mutate the input.

The comparison function is optional. If omitted, default JS sorting is used.

function reverse

Links: source; test/example.

Similar to Array.prototype.reverse. Differences:

  • Takes an arbitrary iterable compatible with #values.
  • Iterable may be nil, equivalent to [].
  • Always creates a new array. Does not mutate the input.

function index

Links: source; test/example.

Signature: (Iter<A>, A => Key | any) => {[Key: A]}.

Takes an arbitrary iterable compatible with #values and returns an index where its values are indexed by the given function, hence the name. The function is called for each value. If the function returns a valid_key, the key-value pair is added to the index. Invalid keys are ignored. If the function returns the same key for multiple values, previous values are lost.

Compare #group which keeps all values for each group, rather than only the last.

function group

Links: source; test/example.

Signature: (Iter<A>, A => Key | any) => {[Key: A[]]}.

Takes an arbitrary iterable compatible with #values and groups its values by keys generated by the given function. The function is called for each value. If the function returns a valid_key, the value is added to the index under that key. Invalid keys are ignored.

Compare #index, which keeps only the last value for each group.

function partition

Links: source; test/example.

Signature: (Iter<A>, A => bool) => [A[], A[]].

Partitions the #values of a given iterable, returning a tuple of two groups: values that satisfy the predicate and the remainder.

function sum

Links: source; test/example.

Signature: (Iter<A>) => fin.

Sums all finite #values of an arbitrary iterable, ignoring all non-finite values.

function zip

Links: source; test/example.

Signature: (Iter<[Key, A]>) => {[Key: A]}.

Similar to Object.fromEntries. Differences:

  • Takes an arbitrary iterable compatible with #values (more flexible).
    • Each value of this iterable must be a key-value pair.
  • Ignores entries where the first element is not a valid_key.
  • Returns a null_prototype_object.
  • Slightly slower.

function setOf

Links: source; test/example.

Syntactic shortcut for creating a Set via variadic call.

import * as i from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/iter.mjs'

i.setOf(10, 20, 30)
// Set{10, 20, 30}

function setFrom

Links: source; test/example.

Converts an arbitrary input to a native Set. Similar to new Set. Differences:

  • If input is already a set: return as-is without copying.
  • Otherwise, create a set of the input's #values.
    • Maps and structs are treated as collections of their values rather than key-value entries.

function setCopy

Links: source; test/example.

Similar to #setFrom: converts an arbitrary input to a set. Difference: always makes a copy. If the original was a set, it's unaffected by mutations of the output.

function mapOf

Links: source; test/example.

Syntactic shortcut for creating a Map with inline keys and values. Shorter and less noisy than either new Map with an array of entries or chained .set calls. The name mirrors Array.of.

function range

Links: source; test/example.

Signature: (min: int, max: int) => int[].

Returns an array of contiguous integers in the range of [min, max). The first value is min, the last value is max - 1.

function span

Links: source; test/example.

Signature: nat => nat[].

Returns an array of the given length, where values are integers from 0. Shortcut for i.range(0, length). Nil length is equivalent to 0.

function times

Links: source; test/example.

Signature: (len: nat, fun: nat => A) => A[].

Takes an array length and a mapping function. Returns an array of the given length, where each element is the result of calling the given function, passing the element's index, starting with 0. Equivalent to i.mapMut(i.span(len), fun).

function repeat

Links: source; test/example.

Signature: (len: nat, val: A) => A[].

Returns an array of the given length where each element is the given value. Equivalent to i.alloc(len).fill(val).

function mapDict

Links: source; test/example.

Signature: ({[Key: A]}, A => B) => {[Key: B]}.

Similar to #map but for dicts.

  • The input must be either nil or a struct. Nil is considered {}.
  • The output is always a plain with the same keys but altered values.
  • The mapping function receives only one argument: each value.
import * as i from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/iter.mjs'
import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/lang.mjs'

i.mapDict({one: 10, two: 20}, l.inc)
// {one: 11, two: 21}

Performance note: dictionary iteration is much slower than array iteration, and should be avoided or minimized.

function pick

Links: source; test/example.

Signature: ({[Key: A]}, A => bool) => {[Key: A]}.

Similar to #filter but for dicts.

  • The input must be either nil or a struct. Nil is considered {}.
  • The output is always a plain. It has only the key-values from the original input for which the given function returned a truthy result.
  • The mapping function receives each value.
import * as i from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/iter.mjs'
import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/lang.mjs'

i.pick({one: -20, two: -10, three: 10, four: 20}, l.isFinPos)
// {three: 10, four: 20}

Performance note: dictionary iteration is much slower than array iteration, and should be avoided or minimized.

function omit

Links: source; test/example.

Signature: ({[Key: A]}, A => bool) => {[Key: A]}.

Similar to #reject but for dicts.

  • The input must be either nil or a struct. Nil is considered {}.
  • The output is always a plain. It has only the key-values from the original input for which the given function returned a falsy result.
  • The mapping function receives each value.
import * as i from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/iter.mjs'
import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/lang.mjs'

i.omit({one: -20, two: -10, three: 10, four: 20}, l.isFinPos)
// {one: -20, two: -10}

Performance note: dictionary iteration is much slower than array iteration, and should be avoided or minimized.

function pickKeys

Links: source; test/example.

Signature: ({[Key: A]}, Iter<Key>) => {[Key: A]}.

Similar to [#pick](#function-pick) but uses keys instead of a function.

  • The input must be either nil or a struct. Nil is considered {}.
  • The output is always a plain. It mirrors the original, but has only "known" given keys, excluding any other.

Performance note: dictionary iteration is much slower than array iteration, and should be avoided or minimized.

function omitKeys

Links: source; test/example.

Signature: ({[Key: A]}, Iter<Key>) => {[Key: A]}.

Similar to [#omit](#function-omit) but uses keys instead of a function.

  • The input must be either nil or a struct. Nil is considered {}.
  • The output is always a plain. It mirrors the original, but has only "unknown" keys, excluding any given keys.

Performance note: dictionary iteration is much slower than array iteration, and should be avoided or minimized.

Undocumented

The following APIs are exported but undocumented. Check iter.mjs.