# Maps
Maps are Deft's persistent hash maps, written `%{ :key value ... }`, and
tagged maps (`%Point{ :x 1 }`) are the same structure carrying a type
name. Every operation here returns a new map and leaves the original
untouched. These bare functions are compiled into every runtime and need
no import. For the broader collection model and destructuring patterns,
see Collections and
Pattern Matching.
## Functions
| Function | Purpose |
|---|---|
| `keys m` | Keys as a list |
| `values m` | Values as a list |
| `entries m` | `@{ key value }` pairs as a list |
| `merge m1 m2` | Merge two maps; later keys win |
| `get m key ?default` | Lookup by key (maps) or index (lists) |
| `assoc m k v ...` | Add or overwrite one or more keys |
| `dissoc m k` | Remove a key |
| `tag m type-name` | Wrap a plain map as a tagged instance |
| `untag m` | Strip the tag, returning a plain map |
| `assoc-in m path v` | Set a value at a nested path |
| `get-in m path ?default` | Look up a value at a nested path |
`get` auto-converts string keys to keywords, so `[get $m "name"]` and
`[get $m :name]` agree. `merge` takes exactly two maps — apply it again
to combine more. `assoc` accepts any number of key-value pairs after the
map, so one call can set several keys.
`keys`, `values`, and `entries` return in insertion order, which is
deterministic for a given sequence of operations but not sorted.
`entries` is the inverse of `from-entries`:
[from-entries [entries $m]] # round-trips back to $m
Paths for `assoc-in` and `get-in` are vectors of keys read from
outermost to innermost. `assoc-in` requires each intermediate level to
already be a map; `get-in` returns the default (`nil` when omitted) as
soon as a level is missing.
`tag` takes a string type name and produces `%Point{ ... }`;
`[type [tag $m "Point"]]` reports `"Point"`. `untag` returns the inner
plain map; pattern matching against tagged maps destructures both the
tag and the fields (see
Pattern Matching).
## Examples
def user %{ :name "Alice" :age 30 :role :admin } $user~name # => "Alice" — field access [get $user :name] # => "Alice" [get $user :email "n/a"] # => "n/a" — default $user # the original is unchanged
Adding, updating, and removing keys:
[assoc $user :email "alice@example.com"] # add a key [assoc $user :age 31 :role :owner] # several pairs at once [dissoc $user :role] # remove a key [merge $user %{ :age 31 :city "Paris" }] # later map wins
Iteration helpers return lists:
[keys $user] # => @{ :name :age :role } [values $user] # => @{ "Alice" 30 :admin } [entries $user] # => @{ @{:name "Alice"} ... }
Nested access and update:
def cfg %{ :db %{ :host "localhost" :port 5432 } } [get-in $cfg @{ :db :host }] # => "localhost" [get-in $cfg @{ :db :user } "root"] # => "root" — default [assoc-in $cfg @{ :db :port } 6543] # => port set to 6543 [assoc-in %{ :a %{} } @{ :a :b } 1] # => %{ :a %{ :b 1 } }
Tagged maps carry a type name and behave like their plain version:
def point [tag %{ :x 10 :y 20 } "Point"] [type $point] # => "Point" $point~x # => 10 [untag $point] # => %{ :x 10 :y 20 }
`assoc`, `dissoc`, and `merge` preserve a tag when given one:
`[assoc $point :z 30]` is still a `%Point`.
## See Also
- Collections — literals, persistence, `from-entries`
- Pattern Matching — tagged-map destructuring
- Lists — `zip`, `from-entries`, positional access
- Sets — the set-shaped collection