8.16 Map Expressions

Creating Maps

Constructing a new map is done by letting an expression K be associated with another expression V:

  1. #{ K => V }

New maps can include multiple associations at construction by listing every association:

  1. #{ K1 => V1, .., Kn => Vn }

An empty map is constructed by not associating any terms with each other:

  1. #{}

All keys and values in the map are terms. Any expression is first evaluated and then the resulting terms are used as key and value respectively.

Keys and values are separated by the => arrow and associations are separated by a comma ,.

Examples:

  1. M0 = #{}, % empty map
  2. M1 = #{a => <<"hello">>}, % single association with literals
  3. M2 = #{1 => 2, b => b}, % multiple associations with literals
  4. M3 = #{k => {A,B}}, % single association with variables
  5. M4 = #{{"w", 1} => f()}. % compound key associated with an evaluated expression

Here, A and B are any expressions and M0 through M4 are the resulting map terms.

If two matching keys are declared, the latter key takes precedence.

Example:

  1. 1> #{1 => a, 1 => b}.
  2. #{1 => b }
  3. 2> #{1.0 => a, 1 => b}.
  4. #{1 => b, 1.0 => a}

The order in which the expressions constructing the keys (and their associated values) are evaluated is not defined. The syntactic order of the key-value pairs in the construction is of no relevance, except in the recently mentioned case of two matching keys.

Updating Maps

Updating a map has a similar syntax as constructing it.

An expression defining the map to be updated, is put in front of the expression defining the keys to be updated and their respective values:

  1. M#{ K => V }

Here M is a term of type map and K and V are any expression.

If key K does not match any existing key in the map, a new association is created from key K to value V.

If key K matches an existing key in map M, its associated value is replaced by the new value V. In both cases, the evaluated map expression returns a new map.

If M is not of type map, an exception of type badmap is thrown.

To only update an existing value, the following syntax is used:

  1. M#{ K := V }

Here M is a term of type map, V is an expression and K is an expression that evaluates to an existing key in M.

If key K does not match any existing keys in map M, an exception of type badarg is triggered at runtime. If a matching key K is present in map M, its associated value is replaced by the new value V, and the evaluated map expression returns a new map.

If M is not of type map, an exception of type badmap is thrown.

Examples:

  1. M0 = #{},
  2. M1 = M0#{a => 0},
  3. M2 = M1#{a => 1, b => 2},
  4. M3 = M2#{"function" => fun() -> f() end},
  5. M4 = M3#{a := 2, b := 3}. % 'a' and 'b' was added in `M1` and `M2`.

Here M0 is any map. It follows that M1 .. M4 are maps as well.

More Examples:

  1. 1> M = #{1 => a}.
  2. #{1 => a }
  3. 2> M#{1.0 => b}.
  4. #{1 => a, 1.0 => b}.
  5. 3> M#{1 := b}.
  6. #{1 => b}
  7. 4> M#{1.0 := b}.
  8. ** exception error: bad argument

As in construction, the order in which the key and value expressions are evaluated is not defined. The syntactic order of the key-value pairs in the update is of no relevance, except in the case where two keys match. In that case, the latter value is used.

Maps in Patterns

Matching of key-value associations from maps is done as follows:

  1. #{ K := V } = M

Here M is any map. The key K must be an expression with bound variables or literals. V can be any pattern with either bound or unbound variables.

If the variable V is unbound, it becomes bound to the value associated with the key K, which must exist in the map M. If the variable V is bound, it must match the value associated with K in M.

Example:

  1. 1> M = #{"tuple" => {1,2}}.
  2. #{"tuple" => {1,2}}
  3. 2> #{"tuple" := {1,B}} = M.
  4. #{"tuple" => {1,2}}
  5. 3> B.
  6. 2.

This binds variable B to integer 2.

Similarly, multiple values from the map can be matched:

  1. #{ K1 := V1, .., Kn := Vn } = M

Here keys K1 .. Kn are any expressions with literals or bound variables. If all keys exist in map M, all variables in V1 .. Vn is matched to the associated values of their respective keys.

If the matching conditions are not met, the match fails, either with:

  • A badmatch exception.

This is if it is used in the context of the match operator as in the example.

  • Or resulting in the next clause being tested in function heads and case expressions.

    Matching in maps only allows for := as delimiters of associations.

    The order in which keys are declared in matching has no relevance.

    Duplicate keys are allowed in matching and match each pattern associated to the keys:

  1. #{ K := V1, K := V2 } = M

Matching an expression against an empty map literal, matches its type but no variables are bound:

  1. #{} = Expr

This expression matches if the expression Expr is of type map, otherwise it fails with an exception badmatch.

Matching Syntax

Matching of literals as keys are allowed in function heads:

  1. %% only start if not_started
  2. handle_call(start, From, #{ state := not_started } = S) ->
  3. ...
  4. {reply, ok, S#{ state := start }};
  5.  
  6. %% only change if started
  7. handle_call(change, From, #{ state := start } = S) ->
  8. ...
  9. {reply, ok, S#{ state := changed }};

Maps in Guards

Maps are allowed in guards as long as all subexpressions are valid guard expressions.

Two guard BIFs handle maps: