Every language has a map; the differences are all in the corners. What happens on a missing key — a nil, an exception, a zero that looks like data? What does assignment share? Whose order does iteration follow? Mica’s answers are the language’s own rules applied without exception, and the missing-key answer is the one worth the trip: it depends on what you asked.
The example is Maps; it assumes dynamic arrays.
The verbs, by import and by address
imp
Map, KeyList, Put, Get, GetOr, Has, Remove, Count, Keys : maps;
var
stock : Map of (string, int64);A Map of (K, V) is typed at both ends, the verbs live in the maps unit,
and mutation travels by address — the same consent rule as
everywhere else:
Put(address stock, "bolts", 250);The verbs
3 kinds in stock
bolts after an update: 240 (still 3 kinds)A repeated key replaces its value in place; the live count never grows on an update.
Two reading moods
Here is the corner most languages get wrong, answered by splitting the
question. Get is for keys the program knows are there. A missing key
is then a bug — first kingdom, as the error
tutorials drew the line — and it stops the
program at once, on every optimization tier:
Mica runtime failure: reason=map_key_missing (16)No nil that dereferences three functions later, no default that
masquerades as data. Has and GetOr ask first, for keys that are
honestly optional, with the default visible at the call:
Asking first, or knowing
do we carry rivets? 0
rivets or a visible default: -1
nuts, known to be there: 900The programmer states which mood each read is in, and each mood keeps its promise. Go’s two-result comma-ok is opt-in — forget it and you get a zero value silently. Mica has no silent road.
Snapshots in insertion order
names := Keys(stock);
for i := 0 to Length(names) - 1 do
WriteLn(" %ls: %lld", names[i], Get(stock, names[i]));Snapshots in insertion order
bolts: 240
nuts: 900Keys and Values answer the surviving entries in insertion order — a
deterministic walk, not a hash order that reshuffles between runs or
releases. Each snapshot is itself a value the caller owns.
A map is a value
backup := stock;
Put(address stock, "bolts", 0);
Remove(address stock, "nuts");A map is a value
original: bolts 0, 1 kinds
backup: bolts 240, 2 kindsThe assignment deep-copies the entries, keys and values both. The original then changed twice, and the backup holds the world as it stood — no hidden sharing, no copy-on-write surprise, the value rule with no exception for containers.
What this does not do
- No
stock["bolts"]sugar. The verbs are imported functions; the bracket stays what it is everywhere — an array index. One fewer thing that looks cheap and is not. - Insertion order is the only order. There is no sorted view; sort a snapshot if you need one.
- The deep copy is real work. Assigning a large map costs what copying a large map costs — the price of no hidden sharing, paid where it is visible.
Try it
git clone https://gitlab.com/mica-lang/mica-container.git
make -C mica-container/examples/Maps runChange the last Get(stock, ...) to ask for "washers" — removed earlier
in the run — and meet map_key_missing at a named line. Then make the same
read a GetOr and decide what the default should be; having to decide is
the feature.
Next
Bit records — the other end of the data spectrum:
named, typed fields packed into exactly N bits, and the case that
dispatches on binary patterns.