Skip to main content

List<T>

Pebble 0.3.1 · all symbols on this page are stable.

A singly-linked list of elements of type T. Maps to Plutus Core's built-in list.

Despite being a cons list at the interpreter level, indexed access xs[i] is essentially free in the on-chain cost model: it lowers to headList (dropList i xs), and dropList is billed by the bit-width of i (constant for any practical index), not by how many cons cells get walked. So unlike with arrays in other languages, you don't need to convert to Array<T> for cheap indexed reads — see Cost and Complexity for the measured numbers.

Literals

const empty: List<int> = [];
const xs: List<int> = [1, 2, 3];

The element type is inferred from context (or from the first element if obvious).

Methods

MethodDescription
length(): intElement count. Walks the spine; O(n) in actual work, charged O(n) by the cost model.
isEmpty(): boolTrue if the list has zero elements.
head(): TFirst element. Fails on empty.
tail(): List<T>Everything after the head. Fails on empty.
prepend(x: T): List<T>New list with x at the front.
drop(n: int): List<T>List with the first n elements removed.
map<A>(f: (T) -> A): List<A>Transform every element. Method-only — there's no std.list.map.
filter(p: (T) -> bool): List<T>Keep only elements satisfying p.
foldr<A>(f: (T, A) -> A, init: A): ARight fold.
foldl<A>(f: (A, T) -> A, init: A): ALeft fold. Tail-recursive; prefer for large lists.
some(p: (T) -> bool): boolTrue if any element satisfies p. Short-circuits.
every(p: (T) -> bool): boolTrue if all elements satisfy p. Short-circuits.
find(p: (T) -> bool): Optional<T>First match wrapped in Some, or None.
equals(eq: (T, T) -> bool, other: List<T>): boolElement-wise equality using eq.

Examples (one per method)

const xs: List<int> = [1, 2, 3, 4, 5];

const n: int = xs.length(); // 5
const e: bool = xs.isEmpty(); // false
const h: int = xs.head(); // 1
const t: List<int> = xs.tail(); // [2,3,4,5]
const ys: List<int> = xs.prepend(0); // [0,1,2,3,4,5]
const dr: List<int> = xs.drop(2); // [3,4,5]
const dbl: List<int> = xs.map((n) => n * 2); // [2,4,6,8,10]
const ev: List<int> = xs.filter((n) => n % 2 == 0); // [2,4]
const sumR: int = xs.foldr((a, acc) => a + acc, 0); // 15
const sumL: int = xs.foldl((acc, a) => acc + a, 0); // 15
const any: bool = xs.some((n) => n > 4); // true
const all: bool = xs.every((n) => n > 0); // true
const got: Optional<int> = xs.find((n) => n == 3); // Some{ value: 3 }
const same: bool = xs.equals(std.int.equals, xs); // true

Pipeline example

struct Input { policy: bytes, name: bytes, amount: int }

const inputs: List<Input> = [/* ... */];

const total = inputs
.filter((i) => i.policy == myPolicy)
.foldl((acc, i) => acc + i.amount, 0);

assert total > 0;

Indexing

xs[i] is sugar for accessing the i-th element. It lowers to headList(dropList(i, xs)). The interpreter does O(i) work walking the spine, but the on-chain cost model bills dropList by the bit-width of i, so for any practical index the charged cost is essentially constant. Don't convert to Array<T> just for indexed readsstd.array.fromList itself is linear in the list length, and that conversion is almost always more expensive than however many list-indexing reads you were trying to optimise. Reserve the conversion for scenarios where you genuinely need many random-access reads against the same compact buffer.

See Cost and Complexity for the measured numbers.

Operators

CategoryOperators
Equality==, != (when T implements equality)

See also