Skip to content

Sets Maps And Sequences

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

Pudu

Sets, Maps, And Sequences

Arrays preserve position, maps associate keys with values, and sets represent membership without duplicates. Choosing among them is a semantic decision: the type should state what observations a caller is allowed to depend on.

Constructing values

let queue = ["compile", "test", "publish"]
let ports = mapOf([("http", 80), ("https", 443)])
let capabilities = #{"read", "write", "read"}

queue keeps all three positions. ports associates each key with a value. capabilities contains two distinct members; repeated input does not create repeated membership.

A set is written #{...}. The literal evaluates every written member in source order and constructs the set afterwards, so duplicate members collapse only after their expressions have run — writing the same expression twice never removes an effect or changes the order in which a failure is reported. setOf remains available and builds the same value from an array.

let ranks = #{3, 1, 2, 1}      // #{1, 2, 3}
let none: Set[Str] = #{}

Iteration and rendering are key-ordered, not insertion-ordered, because the value is the same ordered set the standard library has always used.

An empty Set literal needs type context because it has no member from which to infer T. Maps have no dedicated literal; Map.empty and Set.empty provide generic constructors when the context belongs at the call site.

import Std.Map as Map
import Std.Set as Set

let names: Set[Str] = Set.empty()
let counts: Map[Str, Int] = Map.empty()

Membership is an operator

candidate in container answers whether a set contains a value. It occupies comparison precedence, so it groups the way == does and does not need parentheses in a condition.

import Std.Set as Set

if "publish" in granted { deploy() }

let unknown = Set.filter(&names, fn(name: Str) -> Bool => !(name in known))

The candidate is evaluated before the container, and the operator performs one lookup in the ordered set. It does not iterate, and it does not call user code.

in is deliberately set-only for now. Extending it to strings, arrays, maps, ranges, and program types raises questions about borrowing and about cost that are better settled together than one container at a time; until they are, Str.contains, Array.contains, and Std.Map.containsKey say the same thing without pretending to a general protocol.

Sets state predicates directly

Set code is clearest when written in the vocabulary of membership and algebra.

import Std.Set as Set

fn mayDeploy(granted: Set[Str]) -> Bool {
  let required = setOf(["build", "publish"])
  Set.isSubsetOf(&required, &granted)
}

The standard set module includes isEmpty, equals, intersects, subset and superset tests, bulk insertion and removal, filtering, mapping, union, intersection, difference, and symmetric difference. isSupersetOf(left, right) is defined as isSubsetOf(right, left), preserving one source of truth for the relation.

Prefer contains or a set predicate to a hand-written loop with an early Boolean flag. The direct operation records intent and lets the implementation select the appropriate representation.

Maps preserve association

A map iteration yields (K, V), so the relationship between a key and its value remains explicit.

fn total(scores: Map[Str, Int]) -> Int {
  var out = 0
  for (_, score) in scores { out = out + score }
  out
}

Use get when absence is ordinary and pattern-match its Option result. Use the module's transformation and partition operations when producing another map; converting to parallel key and value arrays creates an index relationship that the type no longer protects.

Arrays own sequence order

Arrays are the appropriate result when position or sorted order is observable. The Std.List module supplies transformation, folding, searching, slicing, grouping, zipping, and sorting operations over arrays. Methods cover the small built-in core; module functions carry the broader algorithm vocabulary.

Lazy sequences

Std.Iter separates a source from its consumption. map, filter, take, drop, zip, and related adapters construct sequence descriptions; consumers such as collection or folding request elements through Sequence.advance.

This distinction matters when a pipeline can stop early. An eager intermediate array performs all upstream work and retains all intermediate values. A lazy sequence asks only for the elements the consumer reaches.

Present performance model

The interpreter stores built-in maps and sets in balanced trees ordered by the runtime's value comparison. size reads the structure's stored count, contains and keyed lookup descend the tree, and iteration yields ascending key order. Standard-library modules written in Pudu may still build larger operations from these primitives, so complexity claims belong to the named operation rather than to the word “collection” in general.

Related

Clone this wiki locally