Elixir v1.3.3 Access behaviour View Source

Key-based access to data structures using the data[key] syntax.

Elixir provides two syntaxes for accessing values. user[:name] is used by dynamic structures, like maps and keywords, while user.name is used by structs. The main difference is that user[:name] won’t raise if the key :name is missing but user.name will raise if there is no :name key.

Besides the cases above, this module provides convenience functions for accessing other structures, like at/1 for lists and elem/1 for tuples. Those functions can be used by the nested update functions in Kernel, such as Kernel.get_in/2, Kernel.put_in/3, Kernel.update_in/3, Kernel.get_and_update_in/3 and friends.

Dynamic lookups

Out of the box, Access works with Keyword and Map:

iex> keywords = [a: 1, b: 2]
iex> keywords[:a]
1

iex> map = %{a: 1, b: 2}
iex> map[:a]
1

iex> star_ratings = %{1.0 => "★", 1.5 => "★☆", 2.0 => "★★"}
iex> star_ratings[1.5]
"★☆"

Access can be combined with Kernel.put_in/3 to put a value in a given key:

iex> map = %{a: 1, b: 2}
iex> put_in map[:a], 3
%{a: 3, b: 2}

This syntax is very convenient as it can be nested arbitrarily:

iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in users["john"][:age], 28
%{"john" => %{age: 28}, "meg" => %{age: 23}}

Furthermore, Access transparently ignores nil values:

iex> keywords = [a: 1, b: 2]
iex> keywords[:c][:unknown]
nil

Since Access is a behaviour, it can be implemented to key-value data structures. The implementation should be added to the module that defines the struct being access. Access requires the key comparison to be implemented using the === operator.

Static lookups

The Access syntax (foo[bar]) cannot be used to access fields in structs, since structs do not implement the Access behaviour by default. It is also design decision: the dynamic access lookup is meant to be used for dynamic key-value structures, like maps and keywords, and not by static ones like structs.

Therefore Elixir provides a static lookup for map and structs fields. Imagine a struct named User with name and age fields. The following would raise:

user = %User{name: "john"}
user[:name]
** (UndefinedFunctionError) undefined function User.fetch/2
   (User does not implement the Access behaviour)

Structs instead use the user.name syntax:

user.name
#=> "john"

The same user.name syntax can also be used by Kernel.put_in/2 to for updating structs fields:

put_in user.name, "mary"
%User{name: "mary"}

Differently from user[:name], user.name is not extensible via a behaviour and is restricted to only maps and structs.

Summing up:

  • user[:name] is used by dynamic structures, is extensible and does not raise on missing keys
  • user.name is used by static structures, it is not extensible and it will raise on missing keys

Accessors

While Elixir provides built-in syntax only for traversing dynamic and static key-value structures, this module provides convenience functions for traversing other structures, like tuples and lists, to be used alongside Kernel.put_in/2 in others.

For instance, given a user with a list of languages, here is how to deeply traverse the map and convert all language names to uppercase:

iex> user = %{name: "john",
...>          languages: [%{name: "elixir", type: :functional},
...>                      %{name: "c", type: :procedural}]}
iex> update_in user, [:languages, Access.all(), :name], &String.upcase/1
%{name: "john",
  languages: [%{name: "ELIXIR", type: :functional},
              %{name: "C", type: :procedural}]}

See the functions key/1, key!/1, elem/1 and all/0 for the current accessors.

Link to this section Summary

Functions

Accesses all the elements in a list

Accesses the element at index (zero based) of a list

Accesses the element at the given index in a tuple

Fetches the container’s value for the given key

Gets the container’s value for the given key

Gets and updates the container’s value for the given key, in a single pass

Accesses the given key in a map/struct

Accesses the given key in a map/struct

Link to this section Types

Link to this type t() View Source
t() :: list | map | nil
Link to this type value() View Source
value() :: any

Link to this section Functions

Accesses all the elements in a list.

Examples

iex> list = [%{name: "john"}, %{name: "mary"}]
iex> get_in(list, [Access.all(), :name])
["john", "mary"]
iex> get_and_update_in(list, [Access.all(), :name], fn
...>   prev -> {prev, String.upcase(prev)}
...> end)
{["john", "mary"], [%{name: "JOHN"}, %{name: "MARY"}]}
iex> pop_in(list, [Access.all(), :name])
{["john", "mary"], [%{}, %{}]}

Here is an example that traverses the list dropping even numbers and multipling odd numbers by 2:

iex> require Integer
iex> get_and_update_in([1, 2, 3, 4, 5], [Access.all], fn
...>   num -> if Integer.is_even(num), do: :pop, else: {num, num * 2}
...> end)
{[1, 2, 3, 4, 5], [2, 6, 10]}

An error is raised if the accessed structure is not a list:

iex> get_in(%{}, [Access.all()])
** (RuntimeError) Access.all/0 expected a list, got: %{}

Accesses the element at index (zero based) of a list.

Examples

iex> list = [%{name: "john"}, %{name: "mary"}]
iex> get_in(list, [Access.at(1), :name])
"mary"
iex> get_and_update_in(list, [Access.at(0), :name], fn
...>   prev -> {prev, String.upcase(prev)}
...> end)
{"john", [%{name: "JOHN"}, %{name: "mary"}]}

at/1 can also be used to pop elements out of a list or a key inside of a list:

iex> list = [%{name: "john"}, %{name: "mary"}]
iex> pop_in(list, [Access.at(0)])
{%{name: "john"}, [%{name: "mary"}]}
iex> pop_in(list, [Access.at(0), :name])
{"john", [%{}, %{name: "mary"}]}

When the index is out of bounds, nil is returned and the update function is never called:

iex> list = [%{name: "john"}, %{name: "mary"}]
iex> get_in(list, [Access.at(10), :name])
nil
iex> get_and_update_in(list, [Access.at(10), :name], fn
...>   prev -> {prev, String.upcase(prev)}
...> end)
{nil, [%{name: "john"}, %{name: "mary"}]}

An error is raised for negative indexes:

iex> get_in([], [Access.at(-1)])
** (FunctionClauseError) no function clause matching in Access.at/1

An error is raised if the accessed structure is not a list:

iex> get_in(%{}, [Access.at(1)])
** (RuntimeError) Access.at/1 expected a list, got: %{}

Accesses the element at the given index in a tuple.

Raises if the index is out of bounds.

Examples

iex> map = %{user: {"john", 27}}
iex> get_in(map, [:user, Access.elem(0)])
"john"
iex> get_and_update_in(map, [:user, Access.elem(0)], fn
...>   prev -> {prev, String.upcase(prev)}
...> end)
{"john", %{user: {"JOHN", 27}}}
iex> pop_in(map, [:user, Access.elem(0)])
** (RuntimeError) cannot pop data from a tuple

An error is raised if the accessed structure is not a tuple:

iex> get_in(%{}, [Access.elem(0)])
** (RuntimeError) Access.elem/1 expected a tuple, got: %{}
Link to this function fetch(container, key) View Source
fetch(t, term) :: {:ok, term} | :error

Fetches the container’s value for the given key.

Link to this function get(container, key, default \\ nil) View Source
get(t, term, term) :: term

Gets the container’s value for the given key.

Link to this function get_and_update(container, key, fun) View Source
get_and_update(t, key, (value -> {get, value})) :: {get, t} when get: var

Gets and updates the container’s value for the given key, in a single pass.

This fun argument receives the value of key (or nil if key is not present) and must return a two-element tuple: the “get” value (the retrieved value, which can be operated on before being returned) and the new value to be stored under key. The fun may also return :pop, implying the current value shall be removed from the map and returned.

The returned value is a tuple with the “get” value returned by fun and a new map with the updated value under key.

Link to this function key(key, default \\ nil) View Source

Accesses the given key in a map/struct.

Uses the default value if the key does not exist or if the value being accessed is nil.

Examples

iex> get_in(%{}, [Access.key(:unknown), Access.key(:name)])
nil
iex> get_in(%{}, [Access.key(:unknown, %{name: "john"}), Access.key(:name)])
"john"
iex> get_in(%{}, [Access.key(:unknown), Access.key(:name, "john")])
"john"

iex> map = %{user: %{name: "john"}}
iex> get_in(map, [Access.key(:unknown), Access.key(:name, "john")])
"john"
iex> get_and_update_in(map, [Access.key(:user), Access.key(:name)], fn
...>   prev -> {prev, String.upcase(prev)}
...> end)
{"john", %{user: %{name: "JOHN"}}}
iex> pop_in(map, [Access.key(:user), Access.key(:name)])
{"john", %{user: %{}}}

An error is raised if the accessed structure is not a map/struct/nil:

iex> get_in([], [Access.key(:foo)])
** (RuntimeError) Access.key/1 expected a map/struct or nil, got: []

Accesses the given key in a map/struct.

Raises if the key does not exist.

Examples

iex> map = %{user: %{name: "john"}}
iex> get_in(map, [Access.key!(:user), Access.key!(:name)])
"john"
iex> get_and_update_in(map, [Access.key!(:user), Access.key!(:name)], fn
...>   prev -> {prev, String.upcase(prev)}
...> end)
{"john", %{user: %{name: "JOHN"}}}
iex> pop_in(map, [Access.key!(:user), Access.key!(:name)])
{"john", %{user: %{}}}
iex> get_in(map, [Access.key!(:user), Access.key!(:unknown)])
** (KeyError) key :unknown not found in: %{name: "john"}

An error is raised if the accessed structure is not a map/struct:

iex> get_in([], [Access.key!(:foo)])
** (RuntimeError) Access.key!/1 expected a map/struct, got: []

Link to this section Callbacks

Link to this callback fetch(t, key) View Source
fetch(t, key) :: {:ok, value} | :error
Link to this callback get_and_update(t, key, function) View Source
get_and_update(t, key, (value -> {value, value} | :pop)) :: {value, t}