View Source Maps

Elixir has two "associative data structures": Maps and Keyword Lists. You might know them as dictionary, map, hash table, key-value store, etc. Let's first look at Maps.

A Map is a key-value store that can contain any value as a key.

map = %{:hello => "World!", 32 => :age, "height" => 190.47}

# If the key is an Atom, you can access its value using the dot-notation
map.hello # => "World!"

# Otherwise, you need to use the bracket-notation
map[32] # => :age
map["height"] # => 190.47

# Or use a helper function
Map.get(map, 32) # => :age

# A `nil` is returned if the key does not exist
map["foo"] # => nil

# Delete one key from the map
Map.delete(map, :hello)
# => %{32 => :age, "height" => 190.47}

# Or delete multiple keys at once
Map.drop(map, [:hello, 32])
# => %{"height" => 190.47}

Maps with Atom keys

If your map contains only Atom keys, you can replace the arrow => with a semicolon :

map = %{hello: "World", age: 32, height: 190.47}
map.age
32

The advantage of this notation is that Elixir raises an error if you try to access a key that doesn't exist:

map = %{hello: "World", age: 32, height: 190.47}
map.agee
** (KeyError) key :agee not found in: %{hello: "World", age: 32, height: 190.47}. Did you mean:

      * :age

Updating a Map

There are a few ways to update a value in a map. They all have their pros and cons:

map = %{age: 32, name: "Peter"}

# This notation updates a map value:
Map.put(map, :age, 33)
# => %{name: "Peter", age: 33}

# But it also adds non-existent keys, which is bad if we make a typo:
Map.put(map, :aeg, 33)
# => %{name: "Peter", age: 32, aeg: 33}

# This notation does not add non-existent keys,
# but it stays silent if we make a typo.
Map.replace(map, :aeg, 33)
# => %{name: "Peter", age: 32}

# This version raises an error instead:
Map.replace!(map, :aeg, 33)
** (KeyError) key :aeg not found in: %{name: "Peter", age: 32}
    (stdlib 6.0) :maps.update(:aeg, 33, %{name: "Peter", age: 32})

Lastly, there is a shorthand notation which comes in handy if you don't want to "pipe" (we'll discuss it later) your output.

map = %{age: 32, name: "Peter"}

# The shorthand notation only updates existing keys:
%{map | age: 33, name: "Pietah"}
# => %{age: 33, name: "Pietah"}

# And raises if the key does not exist
%{map | height: 190.47}
** (KeyError) key :height not found in: %{name: "Peter", age: 32}
    (stdlib 6.0) :maps.update(:height, 190.47, %{name: "Peter", age: 32})
    (stdlib 6.0) erl_eval.erl:465: anonymous fn/2 in :erl_eval.expr/6
    (stdlib 6.0) lists.erl:2146: :lists.foldl/3
    (stdlib 6.0) erl_eval.erl:462: :erl_eval.expr/6
    (elixir 1.17.1) src/elixir.erl:364: :elixir.eval_forms/4
    (elixir 1.17.1) lib/module/parallel_checker.ex:112: Module.ParallelChecker.verify/1
    lib/livebook/runtime/evaluator.ex:629: anonymous fn/3 in Livebook.Runtime.Evaluator.eval/4
    (elixir 1.17.1) lib/code.ex:621: Code.with_diagnostics/2

Caveats

Map keys are not ordered and if you iterate over the elements of a map, you cannot expect that the "first" key will always be the same.

map = %{"hello" => "World", "age" => 32, "height" => 190.47}
Enum.map(map, fn {key, _value} -> IO.inspect(key) end)
"age"
"height"
"hello"

Maps cannot have duplicate keys. If you define duplicate keys, the latter key will overwrite the earlier key and a warning will be shown:

%{name: "Peter", age: 32, name: "Pietah"}
%{name: "Pietah", age: 32}
warning: key :name will be overridden in map