Data Structures
Arrays (Array)
Arrays are ordered, mutable sequences of values.
Literals
Inline with brackets:
... splices another collection or an iterator into the literal; see
Spreading:
See the Array API for methods.
Tuples (Tuple)
Tuples are immutable, ordered sequences of values.
Comma-separated items within parentheses construct a tuple. A single item needs a trailing comma to distinguish it from mere grouping:
Spread a sequence into a tuple with ...:
The Tuple type object also builds a tuple from an iterable:
See the Tuple API for details.
Dictionaries (Dict)
Dictionaries are ordered, mutable key-value mappings. They preserve insertion order and are actually multi-maps: a single key can have multiple values.
Literals
Inline with braces:
Symbol Keys vs String Keys
A literal key: in a dict literal creates a symbol key:
To interpret the key as a variable instead, prefix it with $:
Constants, quoted strings, parenthesized expressions, and other literals are automatically treated as expressions. Values are always treated as full, whitespace-insensitive expressions.
Positional Elements
Dict literals can contain values without explicit keys. These receive incrementing integer keys starting at 0:
Ordering
Dictionaries preserve insertion order. Iteration yields entries in the order they were inserted.
Multi-Map Semantics
Dicts are multi-maps: a key can map to multiple values. This matters in specific cases:
-
Construction: Duplicate keys in a literal or spread preserve all values:
-
Plain indexing (
d[key]) returns only the last (most recently inserted) value for a key. - Plain assignment (
d[key] = value) replaces all values for a key. - Methods like
insert,get, andpoprespect the multi-map nature ofdict
See the Dict API for details.
Records (Record)
Records are immutable sequences of positional items and symbol-keyed items, like a tuple with keys. Read an item by indexing with its symbol or position:
Parentheses make a record rather than a tuple when at least one key is specified. Positional items get integer keys counting from 0:
The record function builds a record from its arguments:
A key may repeat, and a lookup finds its latest value. Iterating a record yields key/value tuples in order, with a positional item's position as its key.
Use with to replace keys and without to remove them in a new record:
with keeps each replaced key at its first position, removes later occurrences,
and appends new keys. without removes all occurrences of the supplied keys.
Both leave the original unchanged.
Spreading a repeated key into a call binds its first value to a parameter of
that name, and a ** or ... rest takes the later ones in order. Without a
rest, the repeats raise UnexpectedKeyError.
See the Record API for details.
Structs (Struct)
Structs are mutable sequences of positional items and symbol-keyed items, like a record whose keys are fields. A struct has no methods of its own, so a name reads or writes the item it keys:
Reading a name with no item raises FieldError. Assigning one replaces its item in place, or adds an item at the end. Calling a name calls its item. Indexing reads and writes items by symbol or position.
The struct function builds a struct from its arguments, and Struct from one
spreadable source, as Record does:
A key appears at most once, so building a struct with a repeated key raises ValueError. Iterating, spreading and unpacking work as for a record.
Since every plain name is an item, operations take the struct as an argument to a class method instead:
The type Struct[S] takes a schema, which types each item. Reading or writing
a name the schema doesn't admit is an error:
def connect target @ Struct[{host: Str, port: Int, ?user: Str}]
echo "$(target.host):$(target.port)"
BaseStruct[S] is the read-only view of a struct. A parameter that only reads
takes a BaseStruct, which accepts structs with narrower items, as
BaseDict does for dicts.
See the Struct API for details.
Sets (Set)
Sets are ordered, mutable collections with unique membership semantics.
Sets do not have a dedicated literal syntax. Construct them with the Set type
object from any iterable:
Iteration preserves insertion order. Adding an existing value is a no-op and does not move it to the end.
See the Set API for methods such as add, contains,
union, and diff.
Spreading
... spreads a value into an array, tuple, dictionary, or record literal, or
into a call's arguments. What it contributes depends on both the value and the
context:
| Context | Spread input | Behavior |
|---|---|---|
| Arguments | Array, tuple, set, Iter |
Positional arguments, including pair elements |
| Arguments | Dict, record, struct | Mixed arguments: sequential integer keys are positional, symbol keys are keywords |
| Array or tuple | Array, tuple, set, Iter |
Positional elements |
| Array or tuple | Dict, record, struct | Key/value pairs |
| Dictionary or record | Array, tuple, set, Iter |
Elements assigned incrementing positional keys |
| Dictionary or record | Dict, record, struct | Preserves keyed and positional entries |
| Any | kv() adapter |
Keyed entries; arrays and tuples reject them |
Spread a dictionary directly to preserve its keys:
Spreading dicts preserves duplicate keys and order. Spreading any Iter,
including a dict or record pair iterator, inserts its elements positionally.
Use kv() when you want an iterable of pairs to spread as key/value entries:
The adapter returned by kv() emits keyed entries in every spread context.
It supports spreading only: it cannot be iterated or used with iterator methods.
A keyed spread into an array or tuple raises an error.
An Iterable without spread support must first be converted to an iterator
with iter(). A rest bound by a pattern can be spread repeatedly, unless it is
the rest of an iterator.