# Collections
Deft ships with three persistent immutable collection types — lists,
maps, and sets — plus mutable arrays for tight loops. All collection
literals produce values; operations return new values rather than
mutating in place (with the explicit exception of `<<` and mutable
arrays).
## Literals
| Type | Syntax | Example |
|---|---|---|
| List | `@{ ... }` | `@{ 1 2 3 }` |
| Map | `%{ ... }` | `%{ :a 1 :b 2 }` |
| Set | `^{ ... }` | `^{ :a :b :c }` |
| Tagged map | `%Tag{ ... }` | `%User{ :name "Alice" }` |
| Array (mutable) | `[array ...]` | `[array 1 2 3]` |
Map keys are typically keywords (`:foo`) but can be strings, numbers,
or any immutable value.
def nums @{ 1 2 3 4 5 } def user %{ :name "Alice" :roles ^{:admin :user} } def colors ^{:red :green :blue}
## Lists
Lists are immutable persistent vectors. Random access is O(log n);
head/tail operations are O(1)-ish.
def xs @{ 10 20 30 } $xs(len) # => 3 $xs(first) # => 10 $xs(last) # => 30 $xs(rest) # => @{ 20 30 } $xs(nth 1) # => 20 $xs(slice 0 2) # => @{ 10 20 } $xs(1..3) # => @{ 20 30 } — range slice, same thing $xs(reverse) # => @{ 30 20 10 }
### Construction
def empty @{} def from-range [range 0 5] # => @{ 0 1 2 3 4 } def combined [append $xs @{ 40 50 }] # => @{ 10 20 30 40 50 }
### Appending
`<<` mutates a local collection in place and returns the mutated
collection. `[append $xs x]` returns a new collection and leaves the
original alone.
def out @{} for x in $input { $out << $x } # preferred for accumulation set fresh [append $xs 99] # pure-functional
See [Syntax and Style](syntax-and-style) for the full append semantics.
### Sorting and uniqueness
[sort @{ 3 1 2 }] # => @{ 1 2 3 } [sort $users { ($a~name) < ($b~name) }] # with comparator [unique @{ 1 1 2 3 3 }] # => @{ 1 2 3 } [flatten @{ @{ 1 2 } @{ 3 4 } }] # => @{ 1 2 3 4 }
### Membership
[3 in? $xs] # => true [in? $xs 3] # => same, prefix form
### Index mutation
`set-nth`, `insert-nth`, and `remove-nth` return new lists:
[set-nth $xs 0 99] # => @{ 99 20 30 } [insert-nth $xs 1 15] # => @{ 10 15 20 30 } [remove-nth $xs 1] # => @{ 10 30 }
## Maps
Maps are persistent hash maps. Lookup, assoc, and dissoc are all
O(log n).
def user %{ :name "Alice" :age 30 :role :admin } $user~name # field access (preferred) [get $user :name] # equivalent [get $user :email "n/a"] # with default [user :name] # IFn form — map is callable
### Adding and removing
`assoc` adds/overwrites keys; `dissoc` removes them; both return new
maps:
[assoc $user :email "alice@example.com"] # add a key [assoc $user :age 31] # overwrite [dissoc $user :role] # remove [assoc-in $user @{ :profile :name } "Al"] # nested update [get-in $user @{ :profile :name }] # nested lookup (nil if missing) [get-in $user @{ :profile :age } :unknown] # nested lookup with default
`merge` combines maps, with later maps winning:
[merge $user %{ :age 31 :email "a@x.com" }]
### Iteration
[keys $user] # => ^{:name :age :role} (as list) [values $user] # => @{ "Alice" 30 :admin } [entries $user] # => @{ @{:name "Alice"} ... } for pair [entries $user] { echo "$pair~0 = $pair~1" }
When iterating a map directly, `for` and pipeline operators yield
`@{key value}` pairs:
for entry in $user { echo "$entry~0 -> $entry~1" } $user |> each { echo "$it~0 -> $it~1" }
### Construction helpers
[from-entries @{ @{:a 1} @{:b 2} }] # => %{ :a 1 :b 2 }
[zip @{ :a :b } @{ 1 2 }] # => %{ :a 1 :b 2 }
## Sets
Sets are persistent hash sets.
def roles ^{:admin :user :guest} ($roles << :owner) # => ^{:admin :user :guest :owner} :admin in? $roles # => true [in? $roles :admin] # prefix form
### Set operations
def a ^{ 1 2 3 } def b ^{ 2 3 4 } [union $a $b] # => ^{ 1 2 3 4 } [intersection $a $b] # => ^{ 2 3 } [difference $a $b] # => ^{ 1 } [subset? ^{1 2} $a] # => true [superset? $a ^{1 2}] # => true
## Strings as Collections
Strings are `ISeq` of characters. Iteration yields single-char strings:
"abc"(len) # => 3 "abc"(first) # => "a" "abc"(rest) # => "bc" for ch in "hello" { echo $ch } "hello" |> map { $it(upper) } |> join "" # => "HELLO"
`slice` and `nth` work on strings too:
"hello"(0..3) # => "hel" "hello"(nth 1) # => "e"
## Higher-Order Operations
All collections support the functional trio via `ISeq` / `IReducible`:
$xs |> map { $it * 2 } # lazy seq — see Lazy Sequences $xs |> filter { ($it % 2) == 0 } |> collect $xs |> reduce 0 { ($acc + $it) } $xs |> find { $it > 20 } # => first match, or nil $xs |> any? { $it > 20 } # => true if any matches $xs |> all? { $it > 0 } # => true if all match $xs |> group { ($it % 2) == 0 } # => %{ :true @{...} :false @{...} }
For the functional deep dive, see [Lazy Sequences](lazy-sequences) and
[Transducers](transducers).
## Mutable Arrays
When you need real mutation in a hot loop, mutable arrays skip the
persistence overhead:
def arr [array 1 2 3] [aset $arr 0 99] # mutates in place, returns nil [aget $arr 0] # => 99
Mutable arrays don't implement the full collection protocol; use them
when performance matters and convert back to a list with `collect`
when you're done.
## Pretty-Printing
`pretty` prints a structured view; `pretty-str` returns it as a string:
[pretty $user] # %{ # :name "Alice" # :age 30 # :role :admin # } set text [pretty-str $user 4] # indent width 4
## Common Pitfalls
- **`$x(len)` vs `[len $x]`** — both work; postfix is preferred for
single-arg receiver calls.
- **`get` with default** — `[get $m key default]`; the default is
returned only when the key is absent.
- **Map iteration order** — keys/values/entries return in insertion
order; the order is deterministic for a given sequence of
operations but not alphabetically sorted.
- **Set literals with duplicate keys** — `^{ :a :a :b }` collapses
to `^{ :a :b }`.
- **Field access on a missing key** — `$m~missing-key` returns `nil`
rather than throwing.
## See Also
- [Lazy Sequences](lazy-sequences) — how `map`/`filter` work
- [Transducers](transducers) — reusable transformation pipelines
- [Pattern Matching](pattern-matching) — destructuring collections in `match`
- [Stdlib](stdlib) — full list of collection operations