ADR-105: The Ledger Says Seq[T] for What Is Produced Step by Step, and Par[T] Where the Steps Run at Once

Status: Accepted Date: September 15, 2026 Related: ADR-288 (the ledger’s type language, whose words these join), ADR-024 (? is the absence of a claim), ADR-277 D6 and D7 (sync and throws after a type, which a step borrows), ADR-094 D2 and D4 (a value walked is a value kept), ADR-025 D1 (a loop’s step can fail, and the function declares it), Part II 12.6 (par_iter and the sync lambda), ADR-172 D1 (pauses), ADR-235 (par_iter), ADR-293 D16-D19 (what a sequence is as a whole, and what takes one)

1. Context

The ledger’s type language has a name, type arguments, a variable, a view, a tuple, a function type and ?. It has no word for what keys(), chars(), io::lines() and xs.map fn … hand back: elements of a type, produced one at a time when asked for, with no length until the end and nothing laid out in memory. Without one those entries say -> ?, and because a method is found through its receiver’s type, everything called on such a value is unfound too.

A container is not this. A Vec[T] has its elements already, a length, and an index; it is walked as often as one likes. A produced sequence is walked once, and each step may do work: a step of io::lines() reads the pipe, so it may pause and may fail, which Part I 3.3 already turns into a throws on the function. What is missing is the type the property hangs on.

2. Decisions

D1 — Seq[T] is a word of the ledger’s type language, and what a step may do stands after it as it stands after a function type.

Changed by ADR-172 D1: a step that pauses says pauses; sync promises it does not, and neither word means nobody said.

Seq[T] is elements of T, produced step by step. sync, pauses and throws after it say what one step may do, with ADR-277 D7’s reading for throws: without it a step cannot fail.

[fn."HashMap::keys"]
signature = "[K, V](self: ref collections::HashMap[K, V]) -> Seq[ref K] sync sized"

[fn."io::lines"]
signature = "() -> Seq[String] pauses throws"

[fn."Seq::collect"]
signature = "[T](self: Seq[T]) -> Vec[T]"

A container keeps its own name. Vec[T], HashMap[K, V] and a range are walked as a Seq by a for, and are not one.

D2 — A Seq is consumed by walking it, and a second walk is refused.

Changed by ADR-293 D18 and D19: every read of a name that holds a sequence takes it, not only a walk, and a range replays, so the refusal (NK2702) never applies to one.

for, collect, count, join, nth and every other method that walks take the Seq by value: it is a value kept (ADR-094 D2), and a use after it is refused in this compiler’s words — this sequence was already walked. That is the whole of the once-only rule; no word carries it. A container is walked by view (ADR-094 D4) and as often as one likes.

D3 — Par[T] is what par_iter() hands back, and a lambda handed to it must be sync.

Changed by ADR-235 D1 and D2: Vec::par_iter is a Par[ref T], and its lambda may neither pause (NK2209) nor change a name bound outside it (NK2107), at both settings.

The one dimension that needs a second word, because a caller’s rule differs: a lambda given to a method on a Par[T] runs on several cores at once and must be sync (Part II 12.6), where one given to a Seq[T] may pause. Otherwise Par[T] has Seq[T]’s surface, and at user_parallelism = no it walks in order on one thread, which Part II 10.7 already promises of a par_fold.

D4 — Neither word is in the surface type grammar.

A program cannot write Seq[T] or Par[T]; it writes keys(), for, collect(), and the ledger does the naming. A function of the program’s own that hands back a produced sequence is a decision about laziness at the surface of its own, and it is not taken here.

D5 — What is not distinguished, on purpose.

Changed by ADR-293 D16: three words after Seq[T] do distinguish these — ends (walkable from the back), sized (length known before the walk) and replays (a walk does not use it up); only the views stay the element type’s.

Whether the elements are views into a buffer is the element type’s (Seq[ref String], with Part I 6.6’s rules); whether the order is stable is the container’s (HashMap has none); whether the length is known is D1’s split. No word for any of the three.

3. Consequences

The chain keys().collect() is a Seq then a Vec, and names.sort() finds its method. throws on a Seq is the one spelling of a step can fail, which is what a loop over io::lines() already costs the function.

What a reader gains is a signature that says what map does to what it is given — [T, U](self: Seq[T], f: fn(T) -> U) -> Seq[U] — where it said ?.

What it costs: the entries of every std function that produces or consumes a sequence, rewritten; a receiver rule in the checker for the two words; and the once-only refusal, which is keeps asked of a Seq.

4. What this does not decide

This site is open source. Improve this page.