Structured values
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.