March Docs

Enum

Enum module: Elixir-style enumeration over List(a).

All functions operate on List(a). The Iterable interface (when runtime dispatch lands) will generalise these to any collection.

Comparator convention: cmp : a -> a -> Bool is curried; cmp(x)(y) = true means x should come before y (x is "less than or equal to" y).

See specs/2026-03-19-sorting-and-enum-design.md for design rationale.

Functions

fnallall(xs : List(a), pred : a -> Bool) : Bool#

Returns true if all elements satisfy pred. Short-circuits on first failure. O(n).

Use all to validate a collection without counting or filtering.
fnanyany(xs : List(a), pred : a -> Bool) : Bool#

Returns true if at least one element satisfies pred. Short-circuits. O(n).

Use any instead of filter + count when you only need a yes/no answer —
any stops at the first match.
fnchunk_bychunk_by(xs : List(a), key : a -> k) : List((k, List(a)))#

Chunks consecutive elements with the same key into (key, [elements]) pairs. O(n). Only CONSECUTIVE equal-key runs are grouped.

This is identical to group_by but named correctly after Elixir's chunk_by.
Use chunk_by when the grouping key changes only at boundaries (e.g. run-length
encoding). For grouping all occurrences of a key, use frequencies/assoc lists.
fnchunk_everychunk_every(xs : List(a), n : {Int | _ > 0}) : List(List(a))#

Split xs into consecutive chunks of exactly n elements each. The final chunk may be smaller than n if len(xs) is not divisible by n. Returns [] for empty xs. O(n).

    chunk_every([1,2,3,4,5], 2)  -- [[1,2], [3,4], [5]]
fncountcount(xs : List(a)) : Int#

Returns the number of elements in the list. O(n).

Prefer this over computing length inside a recursive helper when possible.
fndedupdedup(xs : List(a)) : List(a)#

Remove consecutive duplicate elements (structural equality). O(n).

    dedup([1,1,2,3,3,2])  -- [1,2,3,2]

Only adjacent duplicates are removed. For all-duplicates removal, use uniq.
fndrop_whiledrop_while(xs : List(a), pred : a -> Bool) : List(a)#

Drop elements from the front while pred returns true, return the rest. O(n).

    drop_while([1,2,3,4], fn x -> x < 3)  -- [3,4]
fneacheach(xs : List(a), f : a -> b) : Unit#

Applies f to each element for side effects and returns Unit. O(n).

Use when you want to perform an action (e.g. println) for each element
and don't care about the result. Returns Unit regardless of f's return type.
fnfilterfilter(xs : List(a), pred : a -> Bool) : List(a)#

Returns only the elements for which pred returns true. O(n).

Preserves order and is stable (equal elements stay in original relative order).
fnfindfind(xs : List(a), pred : a -> Bool) : Option(a)#

Returns the first element satisfying pred, or None if none does. O(n).

Use find when you need one element that matches a condition. If you
need all matching elements, use filter instead.
fnflat_mapflat_map(xs : List(a), f : a -> List(b)) : List(b)#

Applies f to each element (f returns a list) and concatenates all results. O(n * m) where m is average sublist length.

Use when each element expands to zero or more output elements (e.g. tokenizing,
expanding ranges). Equivalent to Haskell's concatMap.
fnfoldfold(xs : List(a), zero : b, f : b -> a -> b) : b#

Left fold over a list. Argument order: collection first, init second, callback last (uncurried-collection convention). The callback f is curried as f(acc)(x) = new_acc. O(n).

This is the primitive from which most other Enum functions can be derived.
Use fold when you need to accumulate a single value from all elements.
fnfrequenciesfrequencies(xs : List(a)) : List((a, Int))#

Count occurrences of each element. Returns List((element, count)) in order of first appearance. O(n) average using a hash map for counting.

    frequencies(["a","b","a","c","b","a"])
      -- [("a",3),("b",2),("c",1)]
fnfrequencies_ordfrequencies_ord(xs : List(a), cmp : a -> a -> Bool) : List((a, Int))#

Count occurrences of each element using a comparator. O(n log n). cmp : a -> a -> Bool where cmp(a)(b) = true means a < b. Output order is hash-traversal order.

Prefer this over `frequencies` for large lists of Int, String, or any
orderable type.

    frequencies_ord(["a","b","a","c"], fn a -> fn b -> a < b)
      -- [("a",2),("b",1),("c",1)]  (hash-traversal order)
fngroup_bygroup_by(xs : List(a), key : a -> k) : List((k, List(a)))#

Groups consecutive elements with the same key into (key, [elements]) pairs. O(n).

Note: only CONSECUTIVE equal-key elements are grouped. If the same key
appears non-consecutively, it produces multiple groups (unlike a hash-group-by).
This matches Elixir's chunk_by semantics, not group_by.

Design: consecutive-only grouping avoids the need for a map/hashtable,
keeping this function pure and O(n) without sorting.
fnintersperseintersperse(xs : List(a), sep : a) : List(a)#

Insert sep between every adjacent pair of elements. O(n).

    intersperse([1,2,3], 0)  -- [1,0,2,0,3]
    intersperse([], 0)        -- []
    intersperse([1], 0)       -- [1]
fnintrosort_byintrosort_by(xs : List(a), cmp : a -> a -> Bool) : List(a)#

Sorts xs using Introsort. Unstable, O(n log n) worst-case guaranteed.

Prefer over timsort_by when:
  - worst-case O(n log n) is required (security-sensitive or adversarial input)
  - stability is not needed and you want predictable worst-case behavior
  - you are sorting data with no natural runs
fnmapmap(xs : List(a), f : a -> b) : List(b)#

Applies f to each element and returns a new list of results. O(n).

This is the primary way to transform a collection element-by-element.
fnmax_bymax_by(xs : List(a), f : a -> k) : Option(a)#

Return Some(element) with the maximum f(element) value, or None for empty. Uses > for key comparison. O(n).

    max_by(["apple","fig","banana"], fn s -> string_length(s))
      -- Some("banana")
fnmin_bymin_by(xs : List(a), f : a -> k) : Option(a)#

Return Some(element) with the minimum f(element) value, or None for empty. Uses < for key comparison. O(n).

    min_by(["banana","fig","apple"], fn s -> string_length(s))
      -- Some("fig")
fnproductproduct(xs : List(Int)) : Int#

Product of a list of integers. Returns 1 for empty list. O(n).

    product([1,2,3,4,5])  -- 120
fnreducereduce(xs : List(a), f : a -> a -> a) : Option(a)#

Like fold, but uses the first element as the initial accumulator. Returns None for empty lists, Some(result) otherwise. O(n).

Prefer over fold when there is no natural zero value (e.g. max, min,
string concatenation without a starting value).
fnscanscan(xs, zero, f)#

Like fold but returns a list of all intermediate accumulator values, including the initial value. O(n).

    scan([1,2,3,4], 0, fn acc -> fn x -> acc + x)
      -- [0, 1, 3, 6, 10]

The returned list always has length len(xs) + 1.
fnslideslide(xs : List(a), n : Int) : List(List(a))#

Return all overlapping windows of length n. O(n * m) where m is window size.

    slide([1,2,3,4], 3)  -- [[1,2,3], [2,3,4]]
    slide([1,2], 3)      -- []   (list shorter than window)
fnsort_bysort_by(xs : List(a), cmp : a -> a -> Bool) : List(a)#

Sorts xs by cmp. Stable (preserves relative order of equal elements). Uses Timsort: O(n log n) worst-case, O(n) on already-sorted input.

Prefer this for general-purpose sorting. The stability guarantee means
that if you sort by key A and do by key B, elements with equal B end
remain in A order.

cmp is curried: cmp(x)(y) = true means x should come before y.
fnsort_small_bysort_small_by(xs : List(a), cmp : a -> a -> Bool) : List(a)#

Sorts a small list (n <= 8) using optimal comparison networks. Falls back to mergesort for n > 8. Stable.

Use sort_small_by when:
  - you know the list has at most 8 elements (e.g. sorting tuples, fixed fields)
  - you want provably minimal comparator count for the size
For general sorting, prefer sort_by.
fnsumsum(xs : List(Int)) : Int#

Sum a list of integers. Returns 0 for empty list. O(n).

    sum([1,2,3,4,5])  -- 15
fntake_whiletake_while(xs : List(a), pred : a -> Bool) : List(a)#

Return the longest prefix for which pred returns true. O(n).

    take_while([1,2,3,4], fn x -> x < 3)  -- [1,2]
fntimsort_bytimsort_by(xs : List(a), cmp : a -> a -> Bool) : List(a)#

Sorts xs using Timsort. Stable, O(n log n) worst-case, O(n) on sorted input.

Explicitly names the algorithm for documentation clarity. Prefer sort_by
for application code; use timsort_by when you specifically need stability
and near-linear behavior on natural runs (e.g. merging pre-sorted logs).
fnuniquniq(xs : List(a)) : List(a)#

Remove all duplicate elements, keeping the first occurrence. O(n) average using a hash map for membership. For sorted output, prefer uniq_ord.

    uniq([1,2,1,3,2])  -- [1,2,3]
fnuniq_byuniq_by(xs : List(a), f : a -> k) : List(a)#

Remove duplicates by key function, keeping the first occurrence. O(n) average using a hash map for membership. For large lists where the key type is orderable, uniq_ord with a key-derived comparator also works.

    uniq_by(["alice","bob","anna"], fn s -> string_slice(s, 0, 1))
      -- ["alice","bob"]   (keeps first with each initial letter)
fnuniq_orduniq_ord(xs, cmp)#

Remove duplicates from an ordered list in O(n log n), keeping the first occurrence of each value. Requires a less-than comparator matching Set's convention: cmp : a -> a -> Bool where cmp(a)(b) == true means a < b. Output preserves insertion order.

Prefer this over `uniq` when your element type has a natural ordering
(Int, String, etc.) and the list is large enough that O(n²) is painful.

    uniq_ord([3, 1, 2, 1, 3], fn a -> fn b -> a < b)
    -- [3, 1, 2]
fnwith_indexwith_index(xs : List(a)) : List((a, Int))#

Pair each element with its zero-based index. O(n).

    with_index([10,20,30])  -- [(10,0), (20,1), (30,2)]
fnzipzip(xs : List(a), ys : List(b)) : List((a, b))#

Zip two lists into a list of pairs, stopping at the shorter list. O(min(|a|,|b|)).

    zip([1,2,3], [4,5,6])  -- [(1,4), (2,5), (3,6)]
fnzip_withzip_with(xs : List(a), ys : List(b), f : a -> b -> c) : List(c)#

Zips two lists together applying f to corresponding elements. Stops at the shorter list. O(min(|xs|, |ys|)). f is curried: f(x)(y) returns the combined value.

Use zip_with when you want to combine two collections element-by-element.
Equivalent to Haskell's zipWith.