Maps
A map holds values by key, like a Map in TypeScript, except that it
never changes. Each operation is called as map.<name>, and each one
that changes a map gives you a new one.
| Operation | Gives | Fails | SQL form |
|---|---|---|---|
count(entries) |
Int, at least 0 |
(SELECT count(*) FROM jsonb_object_keys(x)) |
|
get(entries, key) |
V? |
none | |
has(entries, key) |
Bool |
none | |
keys(entries) |
Set<K> |
none | |
values(entries) |
List<V>, in the canonical order of their keys |
none | |
with(entries, key, value) |
Map<K, V> with key set to value, added or replaced |
none | |
without(entries, key) |
Map<K, V> without key |
none | |
merge(first, second) |
Map<K, V> with both; second wins where both have a key |
none | |
filter(entries, (key, value) -> ...) |
Map<K, V>, the entries accepted |
none | |
map_values(entries, (key, value) -> ...) |
Map<K, U>, each key with the value given for it |
none |
- Walk a map through its keys:
for key in map.keys(prices)goes through them in the canonical order.forover the map itself istype.mismatch, and its edit writesmap.keys(<the map>). - An empty map is
[]written where a map is expected, just as a list literal written where a set is expected is a set (Collections). A named map type'smin_lengthchecks that literal's length (type.invalid_literal). Add entries withmap.with. There is no map literal with entries, so a list with items in it, where a map is expected, istype.mismatch. - Named types. As for lists, an operation that gives
V?,Set<K>orList<V>keeps the named type of the keys or values. It doesn't keep a named map type, becausewithoutcan break that type'smin_length. - Records have no dynamic keys. There is no
record[name]: a record's fields are fixed, and each has its own type, such asCustomer.email. Keys known only while the program runs are what aMapis for.
A shop's stock, through every map operation. filter and map_values
give their inline function each entry's key and value, in the canonical
order of the keys:
type Loaf = Text { min_length: 1 max_length: 40}type Stock = Int { min: 0}/// The stock after `count` more of `loaf` arrive.function delivered(stock: Map<Loaf, Stock>, loaf: Loaf, count: Stock) -> Map<Loaf, Stock> { var on_hand: Stock = match map.get(stock, loaf) { some(found) -> found none -> 0 } return map.with(stock, loaf, Stock(on_hand + count))}/// The loaves with none left, in the canonical order of their names: a/// map is walked through its keys.function sold_out(stock: Map<Loaf, Stock>) -> List<Loaf> { var mut out: List<Loaf> = [] for loaf in map.keys(stock) { if (map.get(stock, loaf) == 0) { out = list.append(out, loaf) } } return out}/// How many loaves are on the shelves altogether.function on_shelves(stock: Map<Loaf, Stock>) -> Stock { return Stock(list.sum(map.values(stock)))}/// The loaves to put out: any with some left, but never `withdrawn`.function on_show(stock: Map<Loaf, Stock>, withdrawn: Loaf) -> Map<Loaf, Stock> { return map.filter(stock, (loaf, count) -> count > 0 and loaf != withdrawn)}/// Each loaf's stock once a tray of `per_tray` more arrives for it.function restocked(stock: Map<Loaf, Stock>, per_tray: Stock) -> Map<Loaf, Stock> { return map.map_values(stock, (loaf, count) -> Stock(count + per_tray))}
test "map operations give new maps" { var no_stock: Map<Loaf, Stock> = [] var stock: Map<Loaf, Stock> = map.with(map.with(no_stock, "Rye", 4), "Bap", 0) assert map.count(no_stock) == 0 assert map.count(stock) == 2 assert map.get(stock, "Rye") == 4 assert map.get(stock, "Spelt") == none assert map.has(stock, "Bap") assert not map.has(stock, "Spelt") assert map.keys(stock) == ["Bap", "Rye"] assert map.values(stock) == [0, 4] // in the canonical order of their keys assert map.with(stock, "Rye", 6) == map.with(map.with(no_stock, "Bap", 0), "Rye", 6) assert map.without(stock, "Bap") == map.with(no_stock, "Rye", 4) assert map.without(stock, "Spelt") == stock var fresh: Map<Loaf, Stock> = map.with(no_stock, "Rye", 9) assert map.merge(stock, fresh) == map.with(map.with(no_stock, "Bap", 0), "Rye", 9) assert delivered(stock, "Bap", 12) == map.with(stock, "Bap", 12) assert delivered(stock, "Spelt", 3) == map.with(stock, "Spelt", 3) assert sold_out(stock) == ["Bap"] assert on_shelves(stock) == 4 var three: Map<Loaf, Stock> = map.with(stock, "Spelt", 2) assert on_show(three, "Spelt") == map.with(no_stock, "Rye", 4) assert restocked(stock, 6) == map.with(map.with(no_stock, "Bap", 6), "Rye", 10)}
map.get gives V?, so a key that may be missing is handled where it is
read; map.has says whether it is there without reading it.
Markdown for agents: /docs/maps.md.Built from Jadpo main at a707efb.