Elixir maps made effortless
Tags: elixir, maps, beginners, immutability
Published: 2019-07-09 07:56:06

---
title: Elixir maps made effortless
published: true
description: a brief walk-through of what's hardest about Elixir maps 
tags: Elixir, maps, beginner, immutability
canonical_url: https://alchemist.camp/articles/how-to-update-elixir-maps
---

Getting used to working with Elixir maps can be one of the most painful aspects of really getting comfortable with the language. If you're coming from a language like Java or Ruby, the fact that everything is immutable can be frustrating to deal with. If you're coming from JavaScript, you'll have that problem *and* be spoiled by having native maps (Objects, in JS speak) fit perfectly with JSON.

The good news is, there are really only a couple of points that trip people up!

## Variables are immutable in Elixir
```javascript
// JavaScript
a = {foo: 42}
b = a
b.foo   // equals 42
b.foo = 5
a   // equals {foo: 5}
```
```elixir
# Elixir
a = %{foo: 42}
b = a
b.foo   # equals 42
b.foo = 5   # b and b.foo are immutable so we get an error
# ** (CompileError) iex:5: cannot invoke remote function b.foo/0 inside a match

b = %{b| foo: 5}
b   # now b is reassigned and bound to %{foo: 5}
a.foo   # still equals 42
```
<br/>
### All "updating" of maps involves reassigning a variable to a new map

Here are a few common ways:
- **put** adds a new value to a map: `a = Map.put(a, bar: 5)` <br/>
Now a is `%{foo: 42, bar: 5}`
- **delete** removes a value from a map: `a = Map.delete(a, :foo)` <br/>
Now a is `%{bar: 5}`
- **put_new** works like put, but does nothing if the key already exists:<br/> `a = Map.put_new(a, :bar, 10)` <br/>
Doesn't replace the existing key, so a is still `%{bar: 5}`
- **merge** adds/updates multiple values into a map: <br/> `a = Map.merge(a, %{foo: "stuff", baz: -5})` <br/>
Now a is `%{foo: "stuff", bar: 5, baz: -5}`

Note that since Elixir variables are immutable, the map functions above created new maps instead of changing `a` itself. Without reassigning a to the new maps with the `a =` at the front of each of the examples above, a would be unchanged.

### Map keys can be Strings or Atoms (or anything!)

In Elixir, you'll run into two forms of map keys. Some, like the examples above, are atoms and some are strings. Very rarely, you may find integers, floats or even other types, including nested maps used as keys of maps!

The following are all valid maps:
- `a = %{:some_atom => :foo}`
- `i = %{1 => 52}`
- `f = %{1.5 => i}` - the value of f is now: `%{1.5 => %{1 => 52}}`
- `m = %{f => "wat???"}` - the value of m is now: `%{%{1.5 => %{1 => 52}} => "wat???"}`

**Strings** are the same thing as Erlang binaries.
- They're represented with quotes. <br/>
`"foo"`
- Longer strings or strings with quotation marks in them can be made with sigils  <br/>
`~s{I'm a string made from a sigil and can have "quoted" parts}` 
- In a **key-value form**, string keys use an arrow syntax. <br/>
`%{"foo" => "stuff", "bar" => 5}`
- When being used to **access** values, they use a bracket syntax. <br/>
`a["foo"]` is "stuff" and `b["foo"]` is 5 <br/>

**Atoms** are unique symbols. They don't get garbage collected so don't generate them dynamically.
- When represented **alone**, they start with a colon. <br/>
`:foo`
- In a **key-value form**, they can use the standard arrow syntax *or* simply have a trailing colon. <br/>
`%{foo: "stuff, bar: 5}` is the same as `%{:foo => "stuff", :bar => 5}`
- When being used to **access** values, they can use a dot syntax. <br/>
`a.foo` is the same as `a[:foo]`


### Working with deeply nested maps
The above techniques are enough to do anything with Elixir maps, however immutability makes working with deeply nested maps a bit cumbersome. Intermediate steps would be required to build up the exact structure you want to reassign a given variable to.

Of course you're always free to write your own helpers, but for the 90% case, the built-in `get_in`, `put_in` and related functions will do the job. They're part of Kernel, not Map, because they also operate on other types of data, so they don't have a `Map.` prefix.

```elixir
users = %{
  "sam" => %{age: 22},
  "pat" => %{age: 58}
}

get_in(users, ["sam", :age])
# returns 22

put_in(users["pat"][:age], 28)
# returns %{"sam" => "{age: 22}, "pat" => %{age: 28}}
```

*Request a free email-based Elixir course from [Alchemist.Camp](https://alchemist967856.typeform.com/to/FA0SUF)*