Maps: records with names
A map is a set of keys and values, read by name with maps:get. Updating one key writes a new map and leaves the old one as it was.
A map is a set of named values
A map is written with a hash sign and braces: #{name => "Ada", age => 36}. Each entry is a key and a value with an arrow between them, and keys are usually atoms. Where a tuple is read by position, a map is read by name, so the order of the entries carries no meaning.
Reading an entry is maps:get(key, Map). If the key is missing, that call fails, which is a loud answer rather than a silent empty one. When a missing key is normal, say what to use instead with the three-argument form, maps:get(key, Map, Default).
map_size counts the entries and maps:keys lists the keys. The order of that list is not something to rely on, so sort it when the output should be stable.
Changing one key makes a new map
Updating is written P#{age => 37}. It gives back a fresh map with a new age, and the map named P still says 36. This is the rule from the first chapter in action: values are never changed, only replaced by new ones.
Adding a key uses the same syntax: P#{city => "London"} gives a map with one more entry. Both forms work on a key whether it was there or not, which is why the same arrow serves for update and insert.
A record with a name, a town and a price reads better as a map than as a tuple, because the code says maps:get(price, Order) rather than element(4, Order). The cost is that the compiler cannot check the names for you, so a typo becomes a missing key at run time.
Maps and pattern matching
A map pattern matches on the keys it mentions and ignores the rest: #{town := Town} takes the town out of an order and says nothing about the other fields. That makes a function head that asks only for the parts it needs.
The pin form := is used in patterns, while => is used when building or updating a map. Keeping the two straight is most of what there is to maps.
maps:update_with(key, Fun, Map) is the third way to change an entry: it applies Fun to the current value, and fails if the key is absent unless a default is supplied. It is useful for a counter, where the new value is a function of the old one, and it keeps the read and the write in one call.
Reading, updating, and counting keys
-module(prog).
-export([main/1]).
main(_) ->
P = #{name => "Ada", age => 36},
io:format("name ~s~n", [maps:get(name, P)]),
io:format("age ~p~n", [maps:get(age, P)]),
Older = P#{age => 37},
io:format("old ~p new ~p~n", [maps:get(age, P),
maps:get(age, Older)]),
Q = Older#{city => "London"},
io:format("keys ~p~n", [lists:sort(maps:keys(Q))]),
io:format("size ~p~n", [map_size(Q)]),
io:format("~p~n", [maps:get(missing, Q, none)]).One record, two versions
Run it and check that the first map still says 36 after the second one is made. Then add a `maps:get` for a key that is not there, with a default.
The two-argument maps:get fails on a missing key. That is deliberate and usually what you want, but if a field is genuinely optional use maps:get(Key, Map, Default) and decide in the code what the default should be.
An update is a new map with most of the old one shared
P#{age => 37} does not edit P. It builds a new map with a new entry for age, and the entries that did not change are shared with the old map rather than copied one by one. That is why an update is cheap even when the map holds many keys, and why the old map remains safe to pass to another function.
The arrow form inserts or replaces. The pin form := in an update insists the key already exists: P#{age := 37} works, while P#{city := "London"} fails with badkey. The check is useful when a missing field is a bug rather than a normal case.
A map pattern asks only for the keys it names
#{name := Name} matches any map that has a name key and binds its value; other keys are ignored. That makes a function head that states exactly what it needs, without demanding a map of a particular size. The pattern fails only when a named key is missing.
This is a different tool from maps:get. A pattern is a condition on a function clause or a case branch; maps:get is an expression that fetches one value. Use the pattern when the shape decides which clause runs, and maps:get when the map is already known to be the right kind.
Three map mistakes are worth knowing. The two-argument maps:get fails on a key that is not there, so an optional field needs the three-argument form. Using := to add a new key to an update fails with badkey; use => when the key may be new. And maps:keys has no useful order, so sort it before printing if the output should be stable.
Try these next
- Add a
pricekey to a map and print the updated map and the original side by side. - Use a map pattern to take only the
namefield out of an order and ignore the rest. - Try
maps:geton a missing key with and without a default, and compare the two results.
Update stock without losing the old map
Run it and check that the original stock map still shows the old counts. Then add a `pen` entry with a default and merge a second map into the stock.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does maps:get(age, #{age => 36}) give?
