Core language · Collections & higher-order operations
Collections.
Nift 4.3 adds mutable arrays, stacks, queues, priority queues, insertion-ordered maps and sets, sorted variants, and higher-order transformations powered by first-class callables. JSON arrays and objects participate in the same operations (see JSON data).
Operation reference
Arrays (including JSON arrays), objects and the collection kinds below accept the following operations. Callbacks are named functions or lambdas; on maps they receive (key, value).
| Operation | Applies to | Result | Purpose |
|---|---|---|---|
map(cb) | array | array | Transform each element; non-mutating. |
filter(cb) | array | array | Keep elements where the callback is true; non-mutating. |
reduce(cb, init) | array | value | Fold elements with an explicit accumulator. |
any(cb) | array | boolean | True if any element matches; short-circuits. |
all(cb) | array | boolean | True if every element matches; short-circuits. |
find(cb) | array | value / null | First matching element or null. |
find_index(cb) | array | number | Index of the first match or -1. |
count(cb) | array | number | How many elements match. |
sort_by(cb[, "dir", cb, "dir"...]) | array | array | Stable sorted copy; compound keys support independent asc/desc directions. |
group_by(cb) | array | object | Group elements into key → array. |
group_by_each(cb) | array | object | Place each item into zero or more generated-key groups. |
partition(cb) | array | object | Split once into named matched/unmatched arrays. |
unique_by(cb) | array | array | Deduplicate by derived value, preserving first occurrence. |
min_by(cb) / max_by(cb) | array | value | Single-pass item selection by scalar key. |
count_by(cb) | array | object | Count items by generated scalar key. |
take(n) / drop(n) | array | array | Take a prefix or drop a prefix without mutation. |
chunk(n) | array | array of arrays | Split into positive-sized chunks, retaining the final partial chunk. |
unique() | array | array | Deduplicate using structural equality. |
flatten() | array | array | Flatten one level of nested arrays. |
sum() | array of numbers | number | Total (0 on an empty array). |
min() / max() | array of numbers/strings | value | Extreme value; empty array is an error. |
sort([cb]) | mutable array | array (mutates) | Stable in-place sort, homogeneous or by comparator. |
size() / length() | array / object / collection | number | Element count. |
empty() | array / object / collection / string | boolean | True when empty. |
first() / last() | array | value | Boundary element; empty array is an error. |
contains(v) | array / collection | boolean | Structural membership. |
indexOf(v) | array | number | First index or -1. |
join(sep) | array of scalars | string | Concatenate rendered scalars. |
slice(start[, end]) | array | array | Subarray copy; non-mutating. |
splice(start, del[, repl]) | mutable array | removed array | Remove/replace in place. |
reverse() | mutable array | array (mutates) | Reverse in place. |
push(v) / pop() / insert(i,v) / remove(i) / clear() | mutable array | value / null | Mutating element operations. |
keys() / values() | object | array | Keys or values in insertion order. |
entries() | object | array of {key,value} | Key/value pairs in insertion order. |
from_entries() | array of {key,value} | object | Inverse of entries(); duplicate generated keys are errors. |
index_by(cb) | array | object | Index elements by a unique generated scalar key. |
has(key) / get(key[, default]) | object | boolean / value | Probe and read a key, with an optional default. |
pick(keys) / omit(keys) | object | object | Shallow immutable field projection/exclusion. |
merge(other) | object | object | Non-mutating shallow merge. |
merge_deep(other) | object | object | Recursive object merge; all non-object collisions, including arrays, use RHS. |
set(k,v) / add(v) | map / set | value | Insert a key or member. |
get(k) / remove(k) | map | value | Read or remove a key. |
push(v) / pop() / top() / front() | stack/queue/prique | value | LIFO/FIFO/priority access and removal. |
stringify() / prettify() | serializable value | string | Compact or indented serialization. |
All operations are read-only unless marked as mutating. Empty-collection and type-mismatch behaviour is called out in each section below.
Transformation
map(cb)
Returns a new array with each element replaced by the callback result. Non-mutating; preserves order.
$[doubled := [1, 2, 3].map(x => x * 2)]
$[titles := posts.map(p => p.title)] filter(cb)
Returns a new array containing only the elements for which the callback returns true. Non-mutating; preserves order.
$[evens := [1, 2, 3, 4].filter(x => x % 2 == 0)]
$[published := posts.filter(p => !p.draft)] reduce(cb, init)
Folds the array left-to-right. The callback receives the current accumulator and each element; the initial accumulator is explicit. Returns the final accumulator. The array may be empty, in which case the initial accumulator is returned unchanged.
$[total := orders.reduce((acc, o) => acc + o.total, 0)] any(cb) / all(cb)
any returns true when at least one element matches; all returns true when every element matches. Both short-circuit: on an empty array any is false and all is true.
$[has_drafts := posts.any(p => p.draft)]
$[everywhere := tags.all(t => t != "")] find(cb) / find_index(cb)
find returns the first matching element or null; find_index returns its index or -1.
$[first_draft := posts.find(p => p.draft)]
$[at := [3, 1, 4].find_index(x => x == 4)] count(cb)
Returns how many elements match the callback.
$[n := [1, 2, 3, 4].count(x => x % 2 == 0)] sort_by(cb[, "dir", cb, "dir"...])
Returns a stable, non-mutating sorted copy. The original one-selector form remains ascending. Compound sorting supplies selector/direction pairs; each selector is evaluated once per item and each direction is "asc" or "desc". Items tied on every key retain source order.
$[by_year := posts.sort_by(p => p.year)]
$[featured_newest := posts.sort_by(p => p.featured, "desc", p => p.date, "desc", p => p.title, "asc")] group_by(cb)
Returns an object mapping each scalar key to an array of the elements sharing it. Keys are produced by rendering the callback result.
$[by_year := posts.group_by(p => p.year)] partition(cb)
Evaluates the predicate once per item and returns {matched, unmatched}; both arrays preserve source order. This is useful for featured/rest or published/draft presentation without two filter passes.
$[parts := posts.partition(p => p.featured)]
$[hero := parts.matched.first()]
$[rest := parts.unmatched] unique_by(cb)
Returns the first item for each structurally distinct derived value. Selector evaluation is once per source item and source order is preserved.
$[authors := posts.unique_by(p => p.author_id)] min_by(cb) / max_by(cb)
Select an item in one pass using a homogeneous scalar key. The first source item wins ties; an empty array is an error.
$[oldest := posts.min_by(p => p.date)]
$[newest := posts.max_by(p => p.date)] count_by(cb)
Returns an insertion-ordered object of generated scalar keys to counts, with keys ordered by first occurrence.
$[counts := posts.count_by(p => p.category)] take(n) / drop(n) / chunk(n)
Array-only presentation helpers. take keeps a prefix, drop removes a prefix, and chunk makes positive-sized groups while retaining a final partial group. They are non-mutating; negative/non-integer counts are errors and chunk(0) is an error.
$[hero := posts.take(1)]
$[rest := posts.drop(1)]
$[rows := products.chunk(3)] group_by_each(cb)
For many-to-many metadata, the selector returns an array of scalar keys. An item is added once to every selected group; duplicate keys from one item are deduplicated, groups follow first occurrence, and members retain source order.
$[posts_by_tag := posts.group_by_each(p => p.tags)]
$[nift_posts := posts_by_tag.get("nift", [])] Selection and aggregation
unique()
Returns a new array with structural duplicates removed, keeping first occurrence order.
$[tags := posts.map(p => p.tags).flatten().unique()] flatten()
Returns a new array with one level of nested arrays spliced in.
$[flat := [[1, 2], [3]].flatten()] sum()
Returns the total of an array of numbers (0 on an empty array). Non-numeric elements are an error.
$[total := orders.map(o => o.total).sum()] min() / max()
Return the extreme of a homogeneous array of numbers or strings. Empty arrays are an error; mixed incomparable types are an error.
$[latest := posts.map(p => p.year).max()] first() / last()
Return the boundary element of an array. Empty arrays are an error.
$[newest := posts.sort_by(p => p.year).last()] slice(start[, end]) / join(sep)
slice returns a subarray copy (end exclusive, clamped); join concatenates rendered scalar elements with a separator. Both are non-mutating.
$[part := items.slice(1, 3)]
$[csv := items.join(", ")] contains(v) / indexOf(v)
contains reports structural membership; indexOf returns the first index or -1. Both use structural equality, including for nested data values.
$[items.contains(3)]
$[items.indexOf(3)] size() / length() / empty()
size() and length() return the element count; empty() reports whether it is zero. Valid on arrays, objects and collection kinds, and on strings ("".empty() is true, "".length() is 0).
$[posts.size()]
$[config.empty()] Mutation
push(v) / pop() / insert(i, v) / remove(i)
Mutate a mutable array. push appends; pop removes and returns the last element (error on empty); insert(i, v) inserts at an index (error out of range); remove(i) removes and returns the element at an index.
$[items := [1, 2]]
$[items.push(3)]
$[items.insert(1, 9)]
$[last := items.pop()] splice(start, del[, repl]) / reverse() / sort([cb]) / clear()
splice removes del elements at start (optionally inserting replacement values) and returns the removed ones; reverse reverses in place; sort stably sorts in place (homogeneous ascending or by a comparator); clear empties the array. All mutate the array and require a mutable binding.
$[values := [3, 1, 2]]
$[values.sort()]
$[values.reverse()] Object data
keys() / values() / entries()
Return the object's keys, values, or an array of {key, value} entries, in insertion order. Observable object order is deterministic.
$[names := data.keys()]
$[config.entries()] from_entries()
Reconstructs an insertion-ordered object from the canonical {key, value} records returned by entries(). Generated keys may be strings, numbers or booleans; invalid or duplicate rendered keys are errors, so object construction never silently loses data.
$[public_meta := metadata.entries().filter(e => e.key != "internal").from_entries()]
$[round_trip := metadata.entries().from_entries()] index_by(cb)
Builds an insertion-ordered object keyed by a selector evaluated once per item. It is useful for relationship lookups such as authors by ID. Duplicate or non-scalar generated keys are errors.
$[authors_by_id := authors.index_by(a => a.id)]
$[owner := authors_by_id.get(post.author_id)] pick(keys) / omit(keys)
Return shallow object copies. pick emits existing fields in requested-key order; omit preserves source order while excluding requested fields. Missing keys are ignored and dotted strings remain literal top-level keys.
$[card := page.pick(["title", "description", "url"])]
$[client := metadata.omit(["draft", "internal"])] has(key) / get(key[, default])
has reports whether the key exists; get returns the value, the optional default, or null.
$[config.has("port")]
$[port := config.get("port", 8080)] merge(other)
Returns a new object with other's key/value pairs overlaid on a copy of the receiver. Non-mutating.
$[full := defaults.merge(overrides)] merge_deep(other)
Recursively merges object/object collisions without mutating either input. Every other collision is replaced by the right-hand value; arrays replace rather than concatenate. This keeps layered site/section/page configuration predictable.
$[page_config := defaults.merge_deep(section).merge_deep(frontmatter)] Mutable arrays
$[items := [1, 2, 3]]
$[items.push(4)]
$[items.insert(1, 9)]
$[items.first()]
$[items.last()]
$[items.indexOf(3)]
$[items.contains(3)]
$[last := items.pop()]
$[items.remove(0)]
$[items.clear()] Arrays also expose size() and empty(). indexOf and contains use structural equality, including for nested data values.
Joining, slicing and mutation
$[csv := items.join(", ")]
$[part := items.slice(1, 3)]
$[tail := items.slice(2)]
$[removed := items.splice(1, 2, [8, 9])]
$[items.reverse()] join and slice do not mutate the array. splice(start, delete_count[, replacements]) mutates and returns the removed values; replacements are supplied as an array. reverse() mutates in place.
String parsing and conversion
Nift 4.3 strings include small parsing helpers that compose directly with the expression language:
$[parts := "alpha,beta,,gamma".split(",")]
$[chars := "Nift".split("")]
$[who := " Ada ".trim()]
$[lower := who.to_lower()]
$[position := "archive.tar.gz".last_index_of(".")]
$[price := "120.50".to_double()]
$[count := "42".to_int()] The string methods are length(), split(delimiter), index_of(needle), last_index_of(needle), contains(needle), starts_with(prefix), ends_with(suffix), trim(), trim_start(), trim_end(), to_lower(), to_upper(), replace(old, replacement), substr(pos[, length]), to_int() and to_double().
split() requires a delimiter. An empty delimiter deliberately splits into UTF-8 characters; adjacent, leading and trailing non-empty delimiters preserve empty fields. Numeric conversion is strict: the complete string must be a valid value, so trim whitespace explicitly when needed. Integer and double values expose to_string() for scalar text conversion.
$[value := " 42 ".trim().to_int()]
$[text := value.to_string()]
$[fields := "a,,b,".split(",")] Expression arrays and method chaining
Array literals can contain arbitrary expressions. Elements evaluate left-to-right exactly once, so literals can be used naturally for computed frontend data rather than only JSON-shaped constants.
$[i := 0]
$[values := [i++, i++, "6".to_int(), make_value()]] Read-only methods also compose on literals and expression/call results. Temporary bindings are not required merely to continue a chain:
$[count := " a,b,c ".trim().split(",").size()]
$[upper := ["a", "b"].join(",").to_upper()] Stack, queue and prique
$[s := stack()]
$[s.push("first")]
$[s.push("second")]
$[top := s.top()]
$[top = s.pop()]
$[q := queue()]
$[q.push("first")]
$[q.push("second")]
$[front := q.front()]
$[front = q.pop()]
$[p := prique()]
$[p.push(3)]
$[p.push(1)]
$[p.push(2)]
$[next := p.front()] stack is LIFO, queue is FIFO, and prique maintains scalar priority order. All support size(), empty(), clear() and contains(). Stack and queue equality is structural; prique is identity-bearing, so use same() when identity matters.
Maps
map() preserves insertion order. Updating an existing key does not move it. sorted_map() instead maintains key order.
$[scores := map()]
$[scores.set("Ada", 9)]
$[scores.set("Grace", 10)]
$[scores.get("Ada")]
$[scores.contains("Grace")]
$[scores.remove("Ada")]
$[sorted := sorted_map()]
$[sorted.set("b", 2)]
$[sorted.set("a", 1)] Map keys are booleans, numbers or strings. Map equality compares logical key/value contents and ignores insertion history.
Sets
set() preserves first insertion order; sorted_set() maintains value order.
$[tags := set()]
$[tags.add("nift")]
$[tags.add("docs")]
$[tags.contains("nift")]
$[tags.remove("docs")]
$[numbers := sorted_set()]
$[numbers.add(3)]
$[numbers.add(1)]
$[numbers.add(2)] Set values are booleans, numbers or strings. Set equality compares logical contents rather than insertion order.
Value formatting
Serializable Nift values expose stringify() for a compact representation and prettify() for an indented human-readable representation. Both return ordinary strings, so they can be stored, written to a file or rendered explicitly.
$[compact := items.stringify()]
$[pretty := items.prettify()] The formatter supports ordinary data values and Nift collections while preserving collection kind and map key types. Struct formatting includes public fields. Opaque runtime values such as callables and file streams are not serializable; formatting them is an error rather than exposing an internal reference. prettify() never embeds terminal colour codes in its returned string.
highlight() returns the same safe compact serialized string, but when its result is inspected directly by nift on a colour-capable terminal the REPL syntax-highlights strings, numbers, booleans and null. ANSI colour is a REPL presentation concern and is not embedded in the value returned by highlight().
$[shown := items.highlight()] Iteration
Collections participate directly in @for; Nift keeps iterator machinery internal.
@for(item : queue) { ... }
@for(value : set_values) { ... }
@for((key, value) : mapping) { ... } Higher-order transformations
Arrays and collections can consume named functions or lambdas. Nift 4.3 provides map, filter, reduce, any, all, find and count.
$[values := [1, 2, 3, 4]]
$[doubled := values.map(x => x * 2)]
$[evens := values.filter(x => x % 2 == 0)]
$[total := values.reduce((acc, x) => acc + x, 0)]
$[values.any(x => x == 3)]
$[values.all(x => x > 0)]
$[values.find(x => x > 2)]
$[values.count(x => x % 2 == 0)] any and all short-circuit. find returns the first matching value or null. reduce takes an explicit initial accumulator. For maps, callbacks receive (key, value).
Data-query methods
Nift 4.3 also provides composable methods for JSON/object and array data. Objects expose keys(), values(), entries(), has(key), get(key[, default]), size(), empty() and non-mutating merge(other). Observable object order is deterministic.
$[names := data.keys()]
$[port := config.get("port", 8080)]
$[combined := defaults.merge(overrides)] Arrays add find_index, sort_by, unique, flatten, sum, min, max and group_by. These compose with the existing higher-order operations, so structured JSON can be queried without a separate query language.
$[published := posts.filter(p => !p.draft).sort_by(p => p.date)]
$[total := orders.map(o => o.total).sum()]
$[by_year := posts.group_by(p => p.year)] Sorting arrays
sort() stably mutates a mutable array. With no callback it orders homogeneous numeric or string values ascending; a callable comparator can define another order.
$[values := [3, 1, 2]]
$[values.sort()]
$[descending := [3, 1, 2]]
$[descending.sort((a, b) => a > b)] Equality and identity
Data values use structural equality. Arrays compare recursively, maps compare key/value contents, and sets compare their logical members. Aggregate locations have location identity: two references are same() when they name the same root binding and normalized path. Struct instances and callables are identity-bearing handles, and same(a, b) asks whether two references designate the same location/object.
$[a := [1, 2, [3]]]
$[b := a]
$[c := copy(a)]
$[a == b]
$[same(a, b)]
$[same(a, c)]