Skip to content

Records Sums And Tuples

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

Pudu

Records, Sums, And Tuples

Product types collect values that exist together. Sum types describe alternatives. Pudu gives both forms names, and pattern matching recovers their components without casts or tag tests.

Records

type Point = { x: Int, y: Int }

fn translate(point: Point, dx: Int, dy: Int) -> Point {
  Point{x: point.x + dx, y: point.y + dy}
}

A record declaration is nominal: two declarations with the same fields are distinct types. Field names are part of construction and access, so their order does not carry meaning at a use site. The checker verifies that construction supplies the declared fields with their declared types.

Sum types and variants

type Shape = Circle(Int) | Rectangle(Int, Int) | Empty

fn area(shape: Shape) -> Int {
  match shape {
    case Circle(radius) => 3 * radius * radius
    case Rectangle(width, height) => width * height
    case Empty => 0
  }
}

Each constructor belongs to the declared sum. A variant may carry no payload, positional fields, or a record-shaped payload. The declaration fixes the corresponding construction and pattern form. A match over a closed sum must cover every variant unless a catch-all arm covers the remainder.

Record-shaped variants keep field names at both boundaries:

type Event = Connected{ host: Str, port: Int } | Closed{ reason: Str }

fn describe(event: Event) -> Str {
  match event {
    case Connected{host, port: _} => "connected to " + host
    case Closed{reason} => reason
  }
}

The shorthand field pattern host binds a name with the same spelling. port: _ names the field and discards its value.

Tuples

Tuples are fixed-size heterogeneous products written by position.

fn swap[A, B](pair: (A, B)) -> (B, A) {
  match pair {
    case (left, right) => (right, left)
  }
}

A tuple pattern can be used by match or directly in a for loop head. Ordinary let binds one name and does not destructure a tuple. Tuples are useful at narrow boundaries—returning two related values or representing a map entry. A record is preferable when the positions need enduring names.

Type aliases

type UserId = Int
type Handler = fn(Str) -> Result[Str, Str]
type Pair[A, B] = (A, B)

Aliases are transparent: UserId and Int denote the same type to the checker. They can document an interface but do not create a distinct domain type. A record or single-variant sum creates a nominal boundary when accidental interchange must be rejected.

Recursive shape

Sum and record declarations may refer to named types according to the checker’s recursive type rules. The interpreter represents the resulting values structurally; native layout and deterministic destruction are not yet part of the implementation contract.

Related

Clone this wiki locally