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 colon :
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