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 nowx.len().type/str/int/float/bool/range/inputremain 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:
| Function | Effect |
|---|---|
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)orx.len()?" question. - No method chaining on collections (
sort(filter(xs)), notxs.filter(...).sort()). Accepted for M1. appendmutating its first argument and returningnilis slightly unusual; the alternative (returning the list) invitesxs = 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.