`map` and `array` — collections
map and array are how a rule works with collections. They exist because the rule language
cannot index or iterate one: there is no [0], no for, no comprehension. Every access goes
through a function call, and every one of these returns a default rather than raising.
That constraint shapes what rules look like. A rule does not walk a table; it asks a question about
it — find the row whose type is “principal”, how many rows are there, does this list contain
the code. Where you genuinely need per-row logic, the answer is usually a table read
(Rows) or a rule that fires per row, not a loop.
map.New() → map
Section titled “map.New() → map”Returns a fresh empty map.
map.Of(kv...) → map
Section titled “map.Of(kv...) → map”Builds a map from alternating key/value pairs.
| Parameter | Type | Meaning |
|---|---|---|
kv | any, variadic | key1, value1, key2, value2, … |
rule RecordDecision "attach the decision inputs to the audit trail" { when out.Has("loanAmount") && out.Has("income") then audit.LogWithData("RecordDecision", "affordability inputs", map.Of("amount", out.Get("loanAmount"), "income", out.Get("income"), "ratio", math.Round(to.Num(out.Get("loanAmount")) / to.Num(out.Get("income")), 2)));}An odd number of arguments is a mistake the engine cannot catch for you — count them.
map.Get* — reading with a default
Section titled “map.Get* — reading with a default”| Function | Returns |
|---|---|
map.GetStr(m, key, defaultVal) | string |
map.GetInt(m, key, defaultVal) | int64 |
map.GetFloat(m, key, defaultVal) | float64 |
map.GetBool(m, key, defaultVal) | bool |
map.GetMap(m, key) | map, or nil |
map.GetList(m, key) | list, or nil |
| Parameter | Type | Meaning |
|---|---|---|
m | map | The map to read |
key | string | The key |
defaultVal | matching type | Returned when the key is absent |
rule ReadRowAmount "the amount column, defaulting to zero" { when array.Len(to.List(in.Rows("invoice.lines"))) > 0 then out.Set("firstLineAmount", map.GetFloat(array.First(to.List(in.Rows("invoice.lines"))), "amount", 0.0));}Note the decimal point on 0.0 — the same type-dispatch rule as elsewhere.
map.Set(m, key, value)
Section titled “map.Set(m, key, value)”Writes into a map in place.
| Parameter | Type | Meaning |
|---|---|---|
m | map | The map to modify |
key | string | The key |
value | any | The value |
For results, prefer out.Set — it records the writing rule and
carries attributes. Use map.Set for a map you are assembling as a value.
map.Delete(m, key)
Section titled “map.Delete(m, key)”Removes a key, in place.
| Parameter | Type | Meaning |
|---|---|---|
m | map | The map to modify |
key | string | The key to remove |
map.Has(m, key) → bool
Section titled “map.Has(m, key) → bool”Reports whether the key is present.
| Parameter | Type | Meaning |
|---|---|---|
m | map | The map to test |
key | string | The key |
map.Keys(m) → list of string
Section titled “map.Keys(m) → list of string”Every key in the map.
map.Len(m) → int64
Section titled “map.Len(m) → int64”How many entries.
map.IsEmpty(m) → bool
Section titled “map.IsEmpty(m) → bool”Whether the map has no entries. Distinct from nil, and true for both.
array.New() → list
Section titled “array.New() → list”A fresh empty list.
array.Len(arr) → int64
Section titled “array.Len(arr) → int64”How many elements.
| Parameter | Type | Meaning |
|---|---|---|
arr | list | The list |
The one function you can call on a collection the rule language will not let you index. A read
that returns a list — Records(), Headings(), Items() — can be counted with this even when
nothing else can touch it.
rule TooManyBorrowers "more than four borrowers needs manual review" { when array.Len(to.List(in.Rows("application.borrowers"))) > 4 then findings.Warn("TooManyBorrowers", "underwriting", "More borrowers than the automated path handles");}array.Get(arr, index) → any
Section titled “array.Get(arr, index) → any”The element at index, zero-based.
| Parameter | Type | Meaning |
|---|---|---|
arr | list | The list |
index | int64 | Zero-based position |
array.GetStr(arr, index, defaultVal) → string · array.GetInt(...) → int64 · array.GetFloat(...) → float64
Section titled “array.GetStr(arr, index, defaultVal) → string · array.GetInt(...) → int64 · array.GetFloat(...) → float64”Typed access with a default, for an index that may not exist.
| Parameter | Type | Meaning |
|---|---|---|
arr | list | The list |
index | int64 | Zero-based position |
defaultVal | matching type | Returned when the index is out of range |
array.First(arr) → any · array.Last(arr) → any
Section titled “array.First(arr) → any · array.Last(arr) → any”The first or last element.
| Parameter | Type | Meaning |
|---|---|---|
arr | list | The list |
Last is the one worth remembering — reaching the final element without knowing the length is
otherwise a two-step.
array.Contains(arr, value) → bool · array.ContainsStr(arr, value) → bool
Section titled “array.Contains(arr, value) → bool · array.ContainsStr(arr, value) → bool”Membership. ContainsStr takes a list of strings specifically, which is what most reads produce.
| Parameter | Type | Meaning |
|---|---|---|
arr | list | The list to search |
value | any / string | What to look for |
rule KnownFormType "the classified type is one we handle" { when !array.ContainsStr(vocab.Items("supported_forms"), out.Str("docType", "")) then findings.Error("KnownFormType", "classification", strings.Sprintf("Unsupported form type: %s", out.Str("docType", "")));}array.IndexOf(arr, value) → int64
Section titled “array.IndexOf(arr, value) → int64”The position of the first occurrence, or -1 when absent.
| Parameter | Type | Meaning |
|---|---|---|
arr | list | The list to search |
value | any | What to look for |
array.IsEmpty(arr) → bool
Section titled “array.IsEmpty(arr) → bool”array.Append(arr, value) → list · array.Prepend(arr, value) → list
Section titled “array.Append(arr, value) → list · array.Prepend(arr, value) → list”Return a new list with the element added at the end or the start.
| Parameter | Type | Meaning |
|---|---|---|
arr | list | The starting list |
value | any | What to add |
They return a new list rather than mutating, so the result has to be captured — out.Set it, or
pass it on.
array.Reverse(arr) → list
Section titled “array.Reverse(arr) → list”A new list in reverse order.
array.Slice(arr, start, end) → list
Section titled “array.Slice(arr, start, end) → list”A sub-range, start inclusive and end exclusive.
| Parameter | Type | Meaning |
|---|---|---|
arr | list | The list |
start | int | First index, inclusive |
end | int | Last index, exclusive |
Finding a row
Section titled “Finding a row”The two functions that make table work possible without iteration.
array.FindRowBy(rows, key, value) → map
Section titled “array.FindRowBy(rows, key, value) → map”Returns the first row whose key column equals value, or an empty map.
| Parameter | Type | Meaning |
|---|---|---|
rows | list of map | The rows to search |
key | string | The column name |
value | string | The value to match |
rule PrincipalPaymentPresent "the payment table has a principal row" { when map.Has(array.FindRowBy(to.Rows(bbox.Doc().Table().Records()), "type", "Principal"), "amount") then out.Set("principal", map.GetFloat( array.FindRowBy(to.Rows(bbox.Doc().Table().Records()), "type", "Principal"), "amount", 0.0));}array.FindRowByRegex(rows, key, pattern) → map
Section titled “array.FindRowByRegex(rows, key, pattern) → map”The same, matching the column against a regular expression.
| Parameter | Type | Meaning |
|---|---|---|
rows | list of map | The rows to search |
key | string | The column name |
pattern | string | The regular expression to match |
The form to use when the label varies between documents — "Principal", "Principal & Interest",
"Principal and Interest" — which on extracted tables it usually does.
rule PrincipalRowFlexible "match however this document words the principal row" { when map.Has( array.FindRowByRegex(to.Rows(bbox.Doc().Table().Records()), "type", "(?i)^principal"), "amount") then out.Set("principal", map.GetFloat( array.FindRowByRegex(to.Rows(bbox.Doc().Table().Records()), "type", "(?i)^principal"), "amount", 0.0));}Why there is no loop
Section titled “Why there is no loop”Deliberate. A rule states a condition and an action; a loop inside one is a program, and a program inside a rule is where rule engines stop being reviewable.
Where you find yourself wanting one:
| You want to | Do this instead |
|---|---|
| Act on each row | A Rows read, then hand Records() to out.Set |
| Find one row | array.FindRowBy / FindRowByRegex |
| Count rows meeting a condition | Extract the column, then count |
| Aggregate a column | to.NumList on the column, which skips non-numbers |
| Genuinely iterate with logic | A script, called before the rules run |
See also
Section titled “See also”- Tables and forms — where rows come from
to— conversion —to.Rows,to.List,to.NumList- The rule language — what the grammar does and does not allow