Skip to main content

Overview

Why a new collections library?

Dart's built-in List, Map, and Set are mutable by default. Any code that holds a reference to a collection can silently change it, which forces defensive copying and makes shared state hard to reason about.

Ribs replaces them with persistent, immutable-by-default equivalents providing these benefits:

  • Immutability — every update operation returns a new collection; the original is never modified. Collections can be freely passed between functions and stored in state without defensive copies or surprise mutations.
  • Structural sharing — because values are immutable, new collections can reuse internal nodes from the old one.
  • Null-safe lookupsget(key) returns Option<V> instead of a nullable V?, making absent-key handling explicit and composable rather than a source of NullPointerExceptions.
  • Richer API — operations like updatedWith, groupMap, partitionMap, traverseEither, and subsets are built in, reducing the need for hand-written loops.
  • Integration with ribs — types such as IO, State, and Rill work directly with IList, IMap, and friends; the whole library speaks the same collection language.

Tradeoffs: the API differs from Dart's standard collections — there is a small learning curve. Code that passes collections to third-party libraries expecting a plain List, Map, or Set needs a conversion step (toList(), toMap(), etc.).


The Collection Hierarchy

Every collection type in ribs — IList, IVector, IMap, ISet, and others — is built on a three-level hierarchy of mixins and an abstract class. Understanding this hierarchy explains where each method comes from and how collections interoperate.

RIterableOnce<A>          can be traversed once; the base of everything
├── RIterable<A> can be traversed multiple times; all concrete collections
│ ├── IList<A>
│ ├── IVector<A>
│ ├── ISet<A>
│ └── IMap<K, V> ...and all other concrete types
└── RIterator<A> a single-use cursor; produced by calling .iterator

RIterableOnce

RIterableOnce<A> is the foundation. Any type that mixes in RIterableOnce can produce an RIterator<A> via its iterator getter, and that iterator can be used to traverse the elements exactly once.

The mixin provides a large set of operations that work on any traversable value — regardless of whether it is a full collection or a one-shot cursor. All of these operations consume the RIterator at most once.

Properties

MemberDescription
iteratorReturns an RIterator<A> for traversing the elements
isEmptytrue if the collection contains no elements
nonEmptytrue if the collection contains at least one element
sizeThe number of elements; uses knownSize where available
knownSizeThe size if known without traversal, or -1

Transforming Elements

MemberDescription
map(f)Apply f to every element; return a new collection of results
flatMap(f)Apply f to every element, where f returns a collection; flatten results
filter(p)Keep only elements satisfying predicate p
filterNot(p)Keep only elements that do not satisfy predicate p
collect(f)Apply f to each element, keeping only Some results
collectFirst(f)Apply f to each element, returning the first Some
tapEach(f)Apply f for its side effect on each element; return the collection unchanged

Folding and Reducing

MemberDescription
foldLeft(z, op)Accumulate a result left-to-right, starting with seed z
foldRight(z, op)Accumulate a result right-to-left, starting with seed z
reduce(op)Reduce to a single value with op; throws on empty collection
reduceOption(op)Like reduce, but returns None instead of throwing for empty
reduceLeft(op)Left-to-right reduce
reduceLeftOption(op)Left-to-right reduce returning None on empty
reduceRight(op)Right-to-left reduce
reduceRightOption(op)Right-to-left reduce returning None on empty

Querying

MemberDescription
find(p)Return the first element satisfying p as Some, or None
exists(p)true if any element satisfies p
forall(p)true if all elements satisfy p
count(p)The number of elements satisfying p

Slicing and Scanning

MemberDescription
take(n)The first n elements
drop(n)All elements after the first n
slice(from, until)Elements in the half-open range [from, until)
takeWhile(p)Elements from the start while p holds
dropWhile(p)Elements from the point where p first fails
span(p)A tuple of (takeWhile(p), dropWhile(p))
splitAt(n)A tuple of (take(n), drop(n))
scan(z, op)Running accumulation left-to-right, including the seed
scanLeft(z, op)Same as scan

Converting

MemberDescription
toList()A Dart List<A>
toIList()An IList<A>
toISet()An ISet<A>, with duplicates removed
toIVector()An IVector<A>
toIndexedSeq()An IndexedSeq<A>
mkString(...)A String of elements joined by a separator, with optional start/end delimiters
maxOption(order)The largest element according to order, or None if empty
minOption(order)The smallest element according to order, or None if empty
maxByOption(f, order)The element with the largest projected value, or None if empty
minByOption(f, order)The element with the smallest projected value, or None if empty

RIterable

RIterable<A> extends RIterableOnce<A> and guarantees that the collection can be traversed any number of times. All concrete collection types in ribs (IList, IVector, IMap, ISet, etc.) mix in RIterable. This allows them to provide operations that require multiple passes or access to structural positions such as the head, tail, or last element.

In addition to everything from RIterableOnce, RIterable provides:

Structural Access

MemberDescription
headThe first element; throws if the collection is empty
headOptionThe first element as Some, or None if empty
lastThe last element; throws if the collection is empty
lastOptionThe last element as Some, or None if empty
tailAll elements except the first
initAll elements except the last
tailsAn iterator of progressively shorter suffixes, ending with empty
initsAn iterator of progressively shorter prefixes, ending with empty

Grouping and Partitioning

MemberDescription
groupBy(f)IMap keyed by f(element), values are sub-collections of matching elements
groupMap(key, f)Like groupBy, but also transforms each element with f
groupMapReduce(key, f, reduce)Like groupMap, but combines values sharing a key using reduce
partition(p)A tuple of two collections: elements satisfying p, and those that do not
partitionMap(f)Apply f returning Either; collect Left and Right results into separate collections
grouped(n)An iterator of non-overlapping chunks of size n
sliding(size, step)An iterator of overlapping windows of size size, advancing by step

Combining

MemberDescription
concat(suffix)This collection followed by all elements of suffix
zip(that)Pair corresponding elements; length is the shorter of the two
zipAll(that, thisElem, thatElem)Pair corresponding elements; pad the shorter collection with fill elements
zipWithIndex()Pair each element with its zero-based index
unzip()(on RIterable<(A,B)>) Split a collection of pairs into two collections

Additional Slicing

MemberDescription
dropRight(n)All elements except the last n
takeRight(n)The last n elements
scanRight(z, op)Running accumulation right-to-left

RIterator

RIterator<A> is the concrete cursor type. It mixes in RIterableOnce<A> and adds the classic hasNext/next() interface, inheriting all the transformation and query operations from RIterableOnce on top of it.

An RIterator is single-use: once next() has been called to advance past an element, that element is gone. The full collection API on RIterable is built on top of creating fresh iterators from the underlying data structure.

Core Interface

MemberDescription
hasNexttrue if more elements remain
next()Returns the next element and advances; throws if empty
toDartConverts to a standard Dart Iterator<A>

Static Constructors

RIterator provides factory methods for building iterators without a backing collection:

MethodDescription
RIterator.empty<A>()An iterator with no elements
RIterator.single(a)An iterator over exactly one element
RIterator.fill(n, elem)An iterator that produces elem exactly n times
RIterator.fromDart(it)Wraps a standard Dart Iterator<A>
RIterator.iterate(start, f)Infinite iterator: start, f(start), f(f(start)), …
RIterator.tabulate(n, f)An iterator of n elements produced by f(0), f(1), …, f(n-1)
RIterator.unfold(initial, f)Stateful iterator: apply f to state, emit value and advance state, stop when f returns None

RIterator.iterate and RIterator.unfold are especially useful for building sequences without allocating a collection upfront. unfold is the most general: it threads a piece of state through successive calls to f, emitting one element per step, and stops as soon as f returns None.

Additional Operations on RIterator

Beyond what it inherits from RIterableOnce, RIterator provides:

MemberDescription
concat(xs)This iterator followed by the elements of xs
distinct(f)Deduplicate by key f, preserving first occurrence
distinctBy(f)Same as distinct
grouped(n)Consecutive non-overlapping chunks of size n
sliding(size, step)Overlapping windows
padTo(len, elem)Extend to at least len elements, padding with elem
sameElements(that)true if both iterators produce the same sequence of elements
indexOf(elem)Some(index) of the first occurrence of elem, or None
indexWhere(p)Some(index) of the first element satisfying p, or None
zip(that)Pair elements; stops at the shorter iterator
zipAll(that, ...)Pair elements; pads the shorter one
zipWithIndex()Pair each element with its index