Skip to content

Iteration And Loops

Chris Michael edited this page Sep 2, 2026 · 2 revisions

Pudu

Iteration And Loops

Pudu has four loop forms. while repeats a Boolean condition, while let repeats a refutable pattern, for consumes an iterable value, and loop expresses repetition whose exit determines its value.

Boolean repetition

fn greatestPowerBelow(limit: Int) -> Int {
  var value = 1
  while value * 2 < limit { value = value * 2 }
  value
}

The condition is evaluated before every iteration and must be Bool. A while expression produces ().

Pattern repetition

while let is the direct form for a state that carries either another value or a stopping case. The subject is re-evaluated before each iteration.

fn drain(start: Option[Int]) -> Int {
  var current = start
  var total = 0
  while let Some(value) = current {
    total = total + value
    current = if value > 1 { Some(value - 1) } else { None }
  }
  total
}

The binding value belongs to one successful iteration. It is not visible after the loop. A pattern that cannot fail is rejected because it would spell an unconditional loop indirectly.

Iteration over values

fn sum(values: Array[Int]) -> Int {
  var total = 0
  for value in values { total = total + value }
  total
}

Arrays yield their elements, strings yield characters, sets yield members, and maps yield (key, value) pairs. A tuple can be decomposed directly in the loop head.

for (name, score) in scores {
  print(name + ": " + show(score))
}

Built-in iteration order is deterministic for a given interpreter value. Programs should not use set or map order as a sorting contract; sort an array when order is part of the result.

Value-producing loops

loop continues until control leaves it. A break may carry the value of the loop.

let first = loop {
  let candidate = nextCandidate()
  if acceptable(candidate) { break candidate }
}

All reachable value-carrying breaks must agree on a type. A loop with no reachable break has type Never, which can join an ordinary type at a control-flow boundary.

break, continue, and labels

continue begins the next iteration of the selected loop. break leaves it. Both have type Never at a valid boundary because evaluation does not continue at that expression.

Labels make an outer target explicit in nested loops. Unlabelled control selects the nearest enclosing loop. A label does not create a value or a new variable scope.

The sequence protocol

User-defined iteration implements Std.Iter.Sequence[S, T]. The state type S is explicit and passed as a value.

trait Sequence[S, T] {
  fn begin(self: &Self) -> S
  fn advance(self: &Self, state: S) -> Option[(S, T)]
}

begin creates the initial state. Each advance returns the next state and element, or None to end the sequence. This protocol supports lazy adapters without requiring hidden iterator mutation.

Cost boundary

The current interpreter has substantial per-iteration overhead. Algorithmic improvements still matter—particularly avoiding repeated full scans—but source-level loop timings should not be read as native-backend performance. The benchmark history records evaluator and data-structure costs separately.

Related

Clone this wiki locally