Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

14. Built-in collection operations are free functions (Milestone 1)

  • Status: Accepted — amended by ADR 0016
  • Date: 2026-09-02

Amendment (2026-09-03, Milestone 2): the collection operations are now methods (xs.push(v), m.keys()), and the free functions listed below were removed. len(x) is now x.len(). print / type / str / int / float / bool / range / input remain free functions. See ADR 0016. The Context and Alternatives below still explain why the free-function form was right for Milestone 1.

Context

Korrin has user-defined classes with methods, so x.foo() syntax exists. The question is whether the built-in types — list, map, str — also carry methods (items.append(x), text.upper()), or whether operations on them are plain functions (append(items, x)).

Full method support for built-in types means the interpreter's attribute-access path has to handle every built-in type, each with its own method table, bound- method values for primitives, and decisions about which methods mutate. That is a meaningful amount of surface area for Milestone 1, whose goal is a complete but small language.

Decision

For Milestone 1, operations on built-in types are free functions in the global scope. Attribute access (.name) is defined only for class instances.

The collection functions are:

FunctionEffect
append(list, value)appends value to list; returns nil
pop(list)removes and returns the last element; error if empty
keys(map) / values(map)a list of the map's keys / values, in insertion order
has(map, key)whether key is present
get(map, key, default?)the value for key, or default (or nil) if absent
remove(map, key)removes key, returning its value or nil

len(x) already covers length for all three types.

This is explicitly a Milestone 1 decision. Adding methods to built-in types later is a compatible change (it adds syntax that currently errors); if it happens, these free functions may be kept as aliases or deprecated, decided in a follow-up ADR.

Consequences

  • The interpreter's . handling stays tiny: instances only.
  • Everything is a function call, which is arguably the most "one obvious way" answer for M1 — there is no "is it len(x) or x.len()?" question.
  • No method chaining on collections (sort(filter(xs)), not xs.filter(...).sort()). Accepted for M1.
  • append mutating its first argument and returning nil is slightly unusual; the alternative (returning the list) invites xs = append(xs, y) which would wrongly suggest lists are immutable. The spec is explicit about the mutation.

Alternatives considered

  • Methods on built-in types now. The "right" long-term design, but a lot of interpreter surface for M1 and it forces early decisions (which methods, which mutate, how str's immutability interacts) better made with real usage.
  • A hybrid — methods on list/map, functions elsewhere. Inconsistent, and still needs the primitive-method machinery.