Expressif works with structured values as naturally as it works with scalar values.

The three main structured forms are:

flowchart TD
    A[Structured values] --> B[Array]
    A --> C[Tuple]
    A --> D[Record]

They solve different problems.

Arrays

An array represents a sequence of values.

Example:

{1, 2, 3}

Arrays are appropriate when the number of values can vary and the values represent the same kind of thing.

Typical operations include:

map(...)
filter(...)
adjacent(...)
sum
count

depending on the available function catalog.

Mapping arrays

map transforms every element.

@orders
| map(.amount)
flowchart LR
    A["array<order>"] --> B["map(.amount)"]
    B --> C["array<numeric>"]

The expression passed to map is evaluated against each element.

Filtering arrays

filter keeps elements for which a predicate evaluates to true.

{1, 2, 3, 4}
| filter(greater-than(2))
flowchart LR
    A["{1, 2, 3, 4}"] --> B["filter(greater-than(2))"]
    B --> C["{3, 4}"]

The greater-than(2) predicate is evaluated for each element. Only values for which it returns #true are kept, producing {3, 4}.

Aggregating arrays

Accumulators reduce an array to a result.

@orders
| map(.amount)
| sum
flowchart LR
    A["array<order>"] --> B["map(.amount)"]
    B --> C["array<numeric>"]
    C --> D[sum]
    D --> E[numeric]

Other accumulators can produce text, counts, booleans, or structured results depending on their contract.

Tuples

A tuple represents a fixed set of positional values.

Conceptually:

T("Alice", 42)

Tuple positions are accessed with positional references such as:

$0
$1

Positions are zero-based, so $0 refers to the first value and $1 to the second.

Tuples are useful when a function needs to expose several related values without assigning field names.

For example, an adjacent-window operation can provide two neighboring values as a tuple-like context.

flowchart LR
    A["previous value"] --> C[Tuple context]
    B["current value"] --> C
    C --> D["$0 / $1"]

Records

A record contains named fields.

Conceptually:

{name:="Alice", age:=42}

Fields can contain scalar or structured values.

{
    name:="Alice",
    totals:={10, 20, 30}
}

Fields are accessed by name:

.name
.totals

Constructing records

Record construction is useful when you want to project one structure into another.

For example:

record(
    id:=.id,
    customer:=.name | upper
)

Each field value is itself an expression.

Conceptually:

flowchart TD
    A[Input record] --> B[".id"]
    A --> C[".name | upper"]
    B --> D["id field"]
    C --> E["customer field"]
    D --> F[Output record]
    E --> F

This makes records an important tool for shaping data, not only storing it.

Arrays, tuples, and records are different

Use an array when:

  • there can be zero, one, or many elements;
  • elements are conceptually peers;
  • collection functions should operate over them.

Use a tuple when:

  • the number of positions is fixed;
  • position has meaning;
  • names are unnecessary or would add noise.

Use a record when:

  • fields have distinct meanings;
  • field names are part of the structure;
  • data should be self-describing.

Nested structured values

Structured values can be nested.

An array of records:

{
    {name:="Alice", age:=42},
    {name:="Bob", age:=37}
}

A record containing arrays:

{
    customer:="Alice",
    orders:={10, 20, 30}
}

A tuple can also contain arrays, records, or other tuples where the function contract allows it.

Structured transformations stay value-oriented

Even when an expression processes many elements, the same mental model applies:

flowchart LR
    A[Structured value] --> B[Structured expression]
    B --> C[Result value]

You do not need to think first in terms of loops. Think in terms of transformations over structured values.


This site uses Just the Docs, a documentation theme for Jekyll.