Lua runner with rich builtin stdlib
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
a/docs/table.md

239 lines
7.5 KiB

# `table`
`a` keeps Lua's standard `table` library (`insert`, `remove`, `concat`, `sort`,
`pack`, `unpack`) and adds the higher-order and collection helpers stock Lua
leaves out: map/filter/reduce, merge and deep-copy, aggregates, lookups, and a
read-only proxy. The additions live on the same global `table`.
```lua
local nums = {1, 2, 3, 4, 5}
local evens = table.ifilter(nums, function(n) return n % 2 == 0 end)
local doubled = table.map(nums, function(n) return n * 2 end)
print(table.sum(nums), table.max(nums)) --> 15 5
```
## Sequences vs. general tables
Lua tables are both arrays and maps, so most helpers come in two flavours. The
distinction runs through the whole module:
- **Plain name** (`map`, `filter`, `reduce`) iterates with `pairs` — it visits
**every** key, preserves keys in the result, and the order is unspecified.
- **`i`-prefixed name** (`imap`-style: `ifilter`, `ireduce`, …) iterates with
`ipairs` over the sequence part `1..#t` **in order**, and produces a fresh
gapless sequence.
Reach for the `i` variants when you have an array and want array semantics
(order preserved, no gaps); use the plain ones for maps or when keys matter.
## Transforming
### `table.map(tbl, fn)`
Apply `fn(value)` to every value, **preserving keys**. Order unspecified.
```lua
table.map({a = 1, b = 2}, function(v) return v * 10 end) --> {a = 10, b = 20}
```
### `table.filter(tbl, predicate)`
Keep the entries where `predicate(value)` is truthy, **preserving keys**. On a
sequence this may leave gaps — use `ifilter` to avoid them.
```lua
table.filter({a = 1, b = 2, c = 3}, function(v) return v > 1 end) --> {b = 2, c = 3}
```
### `table.ifilter(tbl, predicate)`
Keep the sequence elements matching `predicate`, producing a new **gapless**
sequence in order.
```lua
table.ifilter({1, 2, 3, 4}, function(v) return v % 2 == 0 end) --> {2, 4}
```
### `table.filterMap(tbl, fn)`
Map and filter in one pass: apply `fn(value)`, and **drop entries where `fn`
returns `nil`**. Keys are preserved (may leave gaps on a sequence).
```lua
-- keep and square only the even numbers
table.filterMap({a = 1, b = 2, c = 4}, function(v)
if v % 2 == 0 then return v * v end
end) --> {b = 4, c = 16}
```
### `table.ifilterMap(tbl, fn)`
The sequence version of `filterMap`: map over `1..#t` in order, drop `nil`
results, and return a **gapless** sequence.
```lua
table.ifilterMap({1, 2, 3, 4}, function(v)
if v % 2 == 0 then return v * 10 end
end) --> {20, 40}
```
## Reducing
### `table.reduce(tbl, fn, init)`
Fold every value into a single accumulator: `acc = fn(acc, value)`, starting from
`init`. Iterates with `pairs`, so order is unspecified — use it for
order-independent folds (sum, product, building a set).
```lua
table.reduce({1, 2, 3}, function(acc, v) return acc + v end, 0) --> 6
```
### `table.ireduce(tbl, fn, init)`
Like `reduce` but folds the sequence part **in order**, for when order matters
(e.g. concatenation, left-to-right composition).
```lua
table.ireduce({"a", "b", "c"}, function(acc, v) return acc .. v end, "") --> "abc"
```
## Aggregates
These walk all values with `pairs`, so they work on sequences and maps alike.
Each returns `nil` for an empty table (except `sum`, which returns `0`), and a
`NaN` value anywhere propagates to the result.
| Function | Result |
|--------------------|-----------------------------------------|
| `table.min(tbl)` | smallest value, or `nil` if empty |
| `table.max(tbl)` | largest value, or `nil` if empty |
| `table.mean(tbl)` | arithmetic mean, or `nil` if empty |
| `table.sum(tbl)` | sum of values, `0` if empty |
```lua
local t = {4, 8, 15, 16, 23, 42}
print(table.min(t), table.max(t)) --> 4 42
print(table.sum(t), table.mean(t)) --> 108 18.0
```
## Querying
### `table.keys(tbl)` / `table.values(tbl)`
Return an array of all keys, or all values. Order is unspecified.
```lua
table.keys({a = 1, b = 2}) --> {"a", "b"} (some order)
table.values({a = 1, b = 2}) --> {1, 2} (matching order)
```
### `table.contains(tbl, value)`
Return `true` if any value equals `value` (compared with `==`).
```lua
table.contains({"red", "green"}, "green") --> true
```
### `table.find(tbl, predicate)`
Return the **first** value matching `predicate`, together with its key:
`(value, key)`, or `(nil, nil)` if none match. Because a matching value could
itself be `false`, test the returned **key** against `nil` to distinguish "found
`false`" from "not found".
```lua
local v, k = table.find({10, 20, 30}, function(x) return x > 15 end)
print(v, k) --> 20 2
```
### `table.isEmpty(tbl)`
Return `true` if the table has no keys at all (faster and more correct than
`#tbl == 0`, which only inspects the sequence part).
```lua
table.isEmpty({}) --> true
table.isEmpty({x = 1}) --> false
```
## Combining and copying
### `table.merge(...)`
Merge any number of tables into a **new** table (a shallow merge). Sequence parts
are **appended** in argument order; other keys are copied, with later arguments
overwriting earlier ones on a collision. Not recursive — for nested merges use
`deepMerge`.
```lua
table.merge({1, 2}, {3, 4}) --> {1, 2, 3, 4} (sequences concatenate)
table.merge({a = 1, b = 2}, {b = 9}) --> {a = 1, b = 9} (later wins)
```
### `table.deepMerge(...)`
Like `merge`, but where the same key holds a table on both sides, those tables
are merged **recursively**. The result shares no tables with the inputs —
everything is deep-copied — so mutating the result never touches the originals.
```lua
table.deepMerge(
{ db = { host = "localhost", port = 5432 } },
{ db = { port = 5433 } }
) --> { db = { host = "localhost", port = 5433 } }
```
### `table.deepCopy(value)`
Return a deep copy of a table, recursively copying nested tables. Metatables are
**not** copied. Non-table values pass through unchanged.
```lua
local original = { list = {1, 2, 3} }
local copy = table.deepCopy(original)
copy.list[1] = 99
print(original.list[1]) --> 1 (unaffected)
```
`deepMerge` and `deepCopy` cap recursion at 100 levels and error beyond that, so
a cyclic table raises rather than looping forever.
### `table.reverse(tbl)`
Return a new array with the sequence elements in reverse order.
```lua
table.reverse({1, 2, 3}) --> {3, 2, 1}
```
## Protecting
### `table.readonly(tbl [, name])`
Return a read-only proxy of `tbl`: reads pass through to the underlying table,
writes raise an error. The optional `name` is included in that error message.
Useful for exposing a constant namespace that callers must not mutate.
```lua
local config = table.readonly({ retries = 3 }, "config")
print(config.retries) --> 3
config.retries = 5 --> error: Attempt to update a read-only table config
```
**Caveat:** the proxy is an empty shell that forwards reads through a metatable,
so `pairs`, `next`, and `#` see **no** contents — it is meant for guarding API
tables you read by known key, not for iterable data.
## Notes
- **Additions, not replacements.** Stock `table.insert`, `remove`, `concat`,
`sort`, `pack`, `unpack` are all still present.
- **Map vs. sequence is the recurring choice.** Plain functions preserve keys via
`pairs` (unspecified order); `i`-prefixed ones produce gapless sequences via
`ipairs` (in order). Pick by whether you have an array or a map.
- **Most helpers return new tables.** `map`, `filter*`, `merge*`, `deepCopy`,
`reverse`, `keys`, `values` never mutate their input.
- **Type-checked.** Passing a non-table where a table is expected raises a clear
error naming the offending function.