# 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
Strings append too — `$s << x` concatenates like `[str $s x]` (any value on the right str-ifies; a nil receiver starts empty):
set s "a"
$s << "b"                                    # => "ab"
$s << 42                                     # => "ab42"

def csv-line {|ws|                           # nil starts an empty string
    set out nil
    for w in $ws { $out << $w << "," }       # chained << works for locals
    $out
}
[csv-line [words "a b c"]]                   # => "a,b,c,"

[append "a" "b"]                             # => "ab"  (pure form)
`<<` on a non-collection receiver (a number, boolean, ...) raises a catchable type error. Chained `<<` works for locals and globals alike; see 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 and 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 — how `map`/`filter` work - Transducers — reusable transformation pipelines - Pattern Matching — destructuring collections in `match` - Stdlib — full list of collection operations