Iterators

Iterators provide a uniform way to traverse collections. They’re at the heart of Ori’s functional data processing.

The Iterator Trait

trait Iterator {
    type Item;
    @next (self) -> (Option<Self.Item>, Self);
}

Key insight: next returns both the next value AND the new iterator state. This enables immutable iteration.

Creating Iterators

Every collection has an .iter() method:

let numbers = [1, 2, 3, 4, 5];
let iter = numbers.iter();

// Manually iterate
let (first, iter) = iter.next();   // (Some(1), ...)
let (second, iter) = iter.next();  // (Some(2), ...)

In practice, you’ll use higher-level methods instead of calling next directly.

Transformation Methods

map — Transform Each Element

let numbers = [1, 2, 3, 4, 5];
let doubled = numbers.iter()
    .map(transform: x -> x * 2)
    .collect();  // [2, 4, 6, 8, 10]

filter — Keep Matching Elements

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let evens = numbers.iter()
    .filter(predicate: x -> x % 2 == 0)
    .collect();  // [2, 4, 6, 8, 10]

flat_map — Transform and Flatten

let words = ["hello", "world"];
let chars = words.iter()
    .flat_map(transform: word -> word.chars())
    .collect();  // ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']

take — First N Elements

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let first_three = numbers.iter()
    .take(count: 3)
    .collect();  // [1, 2, 3]

skip — Skip First N Elements

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let after_three = numbers.iter()
    .skip(count: 3)
    .collect();  // [4, 5, 6, 7, 8, 9, 10]

Combining take and skip

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let middle = numbers.iter()
    .skip(count: 2)
    .take(count: 5)
    .collect();  // [3, 4, 5, 6, 7]

Reduction Methods

fold — Reduce to Single Value

let numbers = [1, 2, 3, 4, 5];
let sum = numbers.iter()
    .fold(initial: 0, op: (acc, x) -> acc + x);  // 15

let product = numbers.iter()
    .fold(initial: 1, op: (acc, x) -> acc * x);  // 120

count — Count Elements

let numbers = [1, 2, 3, 4, 5];
let count = numbers.iter().count();  // 5

let even_count = numbers.iter()
    .filter(predicate: x -> x % 2 == 0)
    .count();  // 2

any — Check If Any Match

let numbers = [1, 2, 3, 4, 5];
numbers.iter().any(predicate: x -> x > 3);   // true
numbers.iter().any(predicate: x -> x > 10);  // false

all — Check If All Match

let numbers = [1, 2, 3, 4, 5];
numbers.iter().all(predicate: x -> x > 0);   // true
numbers.iter().all(predicate: x -> x > 3);   // false

Selection Methods

find — First Matching Element

let numbers = [1, 2, 3, 4, 5];
let found = numbers.iter()
    .find(predicate: x -> x > 3);  // Some(4)

let not_found = numbers.iter()
    .find(predicate: x -> x > 10);  // None

last — Last Element

let numbers = [1, 2, 3, 4, 5];
let last = numbers.iter().last();  // Some(5)

Combining Iterators

zip — Combine Two Iterators

let names = ["Alice", "Bob", "Charlie"];
let ages = [30, 25, 35];

let people = names.iter()
    .zip(other: ages.iter())
    .map(transform: (name, age) -> `{name} is {age}`)
    .collect();  // ["Alice is 30", "Bob is 25", "Charlie is 35"]

chain — Concatenate Iterators

let first = [1, 2, 3];
let second = [4, 5, 6];

let combined = first.iter()
    .chain(other: second.iter())
    .collect();  // [1, 2, 3, 4, 5, 6]

enumerate — Add Indices

let items = ["a", "b", "c"];

for (index, item) in items.iter().enumerate() do
    print(msg: `{index}: {item}`);

// 0: a
// 1: b
// 2: c

flatten — Flatten Nested Iterators

let nested = [[1, 2], [3, 4], [5, 6]];

let flat = nested.iter()
    .flatten()
    .collect();  // [1, 2, 3, 4, 5, 6]

cycle — Repeat Infinitely

let pattern = [1, 2, 3];

let repeated = pattern.iter()
    .cycle()
    .take(count: 7)
    .collect();  // [1, 2, 3, 1, 2, 3, 1]

DoubleEndedIterator

Some iterators can traverse from both ends:

trait DoubleEndedIterator: Iterator {
    @next_back (self) -> (Option<Self.Item>, Self);
}

This enables:

rev — Reverse Iteration

let numbers = [1, 2, 3, 4, 5];
let reversed = numbers.iter()
    .rev()
    .collect();  // [5, 4, 3, 2, 1]

rfind — Find from Back

let numbers = [1, 2, 3, 4, 5];
let last_even = numbers.iter()
    .rfind(predicate: x -> x % 2 == 0);  // Some(4)

rfold — Fold from Back

let numbers = [1, 2, 3];
let result = numbers.iter()
    .rfold(initial: "", op: (acc, x) -> `{acc}{x}`);  // "321"

Chaining Operations

The real power of iterators is chaining:

type Transaction = { amount: float, category: str }

@total_by_category (transactions: [Transaction], category: str) -> float =
    transactions.iter()
        .filter(predicate: t -> t.category == category)
        .map(transform: t -> t.amount)
        .fold(initial: 0.0, op: (sum, amount) -> sum + amount);


@test_total_by_category tests @total_by_category () -> void = {
    let transactions = [
        Transaction { amount: 100.0, category: "food" }
        Transaction { amount: 50.0, category: "transport" }
        Transaction { amount: 75.0, category: "food" }
    ];
    assert_eq(actual: total_by_category(transactions: transactions, category: "food"), expected: 175.0)
}

@top_transactions (transactions: [Transaction], n: int) -> [Transaction] =
    transactions.iter()
        .filter(predicate: t -> t.amount > 0.0)
        .collect()
        .sort_by(key: t -> -t.amount)  // Descending
        .iter()
        .take(count: n)
        .collect();

Lazy Evaluation

Iterators are lazy — they don’t compute until you consume them:

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// This does almost no work — just sets up the pipeline
let pipeline = numbers.iter()
    .map(transform: x -> {
        print(msg: `Processing {x}`);
        x * 2
    })
    .filter(predicate: x -> x > 10);

// Work happens here when we consume the iterator
let result = pipeline.take(count: 2).collect();

// Prints: Processing 1, Processing 2, ..., Processing 6
// Result: [12, 14]

Notice only the elements needed are processed.

The Iterable Trait

Types that can be iterated implement Iterable:

trait Iterable {
    type Item;
    @iter (self) -> impl Iterator;
}

All standard collections implement this:

  • [T] — lists
  • {K: V} — maps (iterates over (K, V) tuples)
  • Set<T> — sets
  • Ranges

The Collect Trait

Convert iterators back to collections:

trait Collect<T> {
    @from_iter (iter: impl Iterator) -> Self;
}

The .collect() method uses type inference:

let numbers = [1, 2, 3, 4, 5];

// Collect to list (inferred)
let doubled: [int] = numbers.iter().map(transform: x -> x * 2).collect();

// Collect to set
let unique: Set<int> = [1, 2, 2, 3, 3, 3].iter().collect();

Custom Iterators

Create your own iterator by implementing the trait:

type Counter = { current: int, max: int }

impl Counter: Iterator {
    type Item = int;

    @next (self) -> (Option<int>, Counter) = {
        if self.current >= self.max then
            (None, self)
        else
            (Some(self.current), Counter { current: self.current + 1, max: self.max })
    }
}

@count_from (start: int, end: int) -> Counter =
    Counter { current: start, max: end };

// Use like any iterator
let numbers = count_from(start: 1, end: 5).collect();  // [1, 2, 3, 4]

@test_counter tests @count_from () -> void = {
    let counter = count_from(start: 0, end: 3);
    let result = counter.collect();
    assert_eq(actual: result, expected: [0, 1, 2])
}

Fused Guarantee

Ori iterators are fused: once next() returns None, it always returns None:

let numbers = [1, 2];
let iter = numbers.iter();

let (a, iter) = iter.next();  // Some(1)
let (b, iter) = iter.next();  // Some(2)
let (c, iter) = iter.next();  // None
let (d, iter) = iter.next();  // None (always)

For Loop Desugaring

For loops desugar to iterator operations:

// This for loop
for x in items do
    process(x: x);

// Desugars to
let iter = items.iter();
loop {
    let (maybe_x, iter) = iter.next();
    match maybe_x {
        Some(x) -> process(x: x)
        None -> break
    }
}

And for...yield:

// This for loop
let result = for x in items yield x * 2;

// Desugars to
let result = items.iter().map(transform: x -> x * 2).collect();

Common Patterns

Pagination

@paginate<T> (items: [T], page: int, page_size: int) -> [T] =
    items.iter()
        .skip(count: page * page_size)
        .take(count: page_size)
        .collect()

@test_paginate tests @paginate () -> void = {
    let items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    assert_eq(actual: paginate(items: items, page: 0, page_size: 3), expected: [1, 2, 3]);
    assert_eq(actual: paginate(items: items, page: 1, page_size: 3), expected: [4, 5, 6]);
    assert_eq(actual: paginate(items: items, page: 2, page_size: 3), expected: [7, 8, 9])
}

Grouping

@group_by<T, K: Eq + Hashable> (items: [T], key: (T) -> K) -> {K: [T]} = {
    let groups: {K: [T]} = {};
    for item in items do {
        let k = key(item);
        let current = groups[k] ?? [];
        groups = { ...groups, k: [...current, item] }
    };
    groups
}

Windowing

@windows<T: Clone> (items: [T], size: int) -> [[T]] = {
    if size <= 0 || size > len(collection: items) then return []

    for i in 0..(len(collection: items) - size + 1) yield
        items.iter()
            .skip(count: i)
            .take(count: size)
            .collect()
}

@test_windows tests @windows () -> void = {
    let items = [1, 2, 3, 4, 5];
    assert_eq(actual: windows(items: items, size: 3), expected: [[1, 2, 3], [2, 3, 4], [3, 4, 5]])
}

Deduplication

@dedupe<T: Eq> (items: [T]) -> [T] = {
    let seen: [T] = [];
    for item in items do
        if !seen.iter().any(predicate: x -> x == item) then
            seen = [...seen, item];
    seen
}

Complete Example

type Order = {
    id: int,
    customer: str,
    items: [OrderItem],
    status: OrderStatus,
}

type OrderItem = { product: str, quantity: int, price: float }

type OrderStatus = Pending | Shipped | Delivered | Cancelled;


type OrderSummary = {
    total_orders: int,
    total_revenue: float,
    orders_by_status: {str: int},
    top_customers: [(str, float)],
}

@order_total (order: Order) -> float =
    order.items.iter()
        .map(transform: item -> item.quantity as float * item.price)
        .fold(initial: 0.0, op: (a, b) -> a + b);


@test_order_total tests @order_total () -> void = {
    let order = Order {
        id: 1
        customer: "Alice"
        items: [
            OrderItem { product: "Widget", quantity: 2, price: 10.0 }
            OrderItem { product: "Gadget", quantity: 1, price: 25.0 }
        ]
        status: Pending
    };
    assert_eq(actual: order_total(order: order), expected: 45.0)
}

@status_to_str (status: OrderStatus) -> str = match status {
    Pending -> "pending"
    Shipped -> "shipped"
    Delivered -> "delivered"
    Cancelled -> "cancelled"
}

@summarize_orders (orders: [Order]) -> OrderSummary = {
    // Filter out cancelled orders for revenue
    let active_orders = orders.iter()
        .filter(predicate: o -> match o.status { Cancelled -> false, _ -> true})
        .collect();

    // Calculate total revenue
    let total_revenue = active_orders.iter()
        .map(transform: o -> order_total(order: o))
        .fold(initial: 0.0, op: (a, b) -> a + b);

    // Count by status
    let status_counts: {str: int} = {};
    for order in orders do {
        let key = status_to_str(status: order.status);
        let current = status_counts[key] ?? 0;
        status_counts = { ...status_counts, key: current + 1 }
    };

    // Top customers by spending
    let customer_spending: {str: float} = {};
    for order in active_orders do {
        let total = order_total(order: order);
        let current = customer_spending[order.customer] ?? 0.0;
        customer_spending = { ...customer_spending, order.customer: current + total }
    };

    let top_customers = customer_spending.iter()
        .collect()
        .sort_by(key: (_, spending) -> -spending)
        .iter()
        .take(count: 5)
        .collect();

    OrderSummary {
        total_orders: len(collection: orders)
        total_revenue
        orders_by_status: status_counts
        top_customers
    }
}

@test_summarize_orders tests @summarize_orders () -> void = {
    let orders = [
        Order {
            id: 1
            customer: "Alice"
            items: [OrderItem { product: "A", quantity: 1, price: 100.0 }]
            status: Delivered
        }
        Order {
            id: 2
            customer: "Bob"
            items: [OrderItem { product: "B", quantity: 2, price: 50.0 }]
            status: Shipped
        }
        Order {
            id: 3
            customer: "Alice"
            items: [OrderItem { product: "C", quantity: 1, price: 75.0 }]
            status: Cancelled
        }
    ];

    let summary = summarize_orders(orders: orders);
    assert_eq(actual: summary.total_orders, expected: 3);
    assert_eq(actual: summary.total_revenue, expected: 200.0)  // Excluding cancelled
}

Quick Reference

Transform

.map(transform: x -> ...)
.filter(predicate: x -> ...)
.flat_map(transform: x -> ...)
.take(count: n)
.skip(count: n)

Reduce

.fold(initial: val, op: (acc, x) -> ...)
.count()
.any(predicate: x -> ...)
.all(predicate: x -> ...)

Find

.find(predicate: x -> ...)
.last()

Combine

.zip(other: iter)
.chain(other: iter)
.enumerate()
.flatten()
.cycle()

Reverse (DoubleEndedIterator)

.rev()
.rfind(predicate: x -> ...)
.rfold(initial: val, op: (acc, x) -> ...)

Collect

.collect()

What’s Next

Now that you understand iterators: