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

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.

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.

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).

fnfoldfold(zero : b, xs : List(a), f : b -> a -> b) : b#

Left fold over a list. f is curried: f(acc)(x) returns the new accumulator. 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.

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).

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.

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.

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.

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.

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.

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.

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.

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.

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).

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
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.

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) : 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]]

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)]

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)]

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.

fnuniquniq(xs : List(a)) : List(a)#

Remove all duplicate elements, keeping the first occurrence. O(n²).

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²).

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

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]

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)

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]

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]

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")

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")

fnsumsum(xs : List(Int)) : Int#

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

sum([1,2,3,4,5]) -- 15

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

fnfrequenciesfrequencies(xs : List(a)) : List((a, Int))#

Count occurrences of each element. Returns List((element, count)) association list in order of first appearance. O(n²) in unique elements.

frequencies(["a","b","a","c","b","a"]) -- [("a",3),("b",2),("c",1)]