Elixir v1.8.0-rc.1 Access behaviour View Source

Key-based access to data structures.

Elixir supports three main key-value constructs: keywords, maps, and structs. It also supports two mechanisms to access those keys: by brackets (via data[key]) and by dot-syntax (via data.field).

In the next section we will briefly recap the key-value constructs and then discuss the access mechanisms.

Key-value constructs

Elixir provides three main key-value constructs, summarized below:

  • keyword lists - they are lists of two-element tuples where the first element is an atom. Commonly written in the [key: value] syntax, they support only atom keys. Keyword lists are used almost exclusively to pass options to functions and macros. They keep the user ordering and allow duplicate keys. See the Keyword module.

  • maps - they are the "go to" key-value data structure in Elixir. They are capable of supporting billions of keys of any type. They are written using the %{key => value} syntax and also support the %{key: value} syntax when the keys are atoms. They do not have any specified ordering and do not allow duplicate keys. See the Map module.

  • structs - they are named maps with a pre-determined set of keys. They are defined with defstruct/1 and written using the %StructName{key: value} syntax.

Key-based accessors

Elixir provides two mechanisms to access data structures by key, described next.

Bracket-based access

The data[key] syntax is used to access data structures with a dynamic number of keys, such as keywords and maps. The key can be of any type. The bracket-based access syntax returns nil if the key does not exist:

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

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

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

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, the bracket-based access syntax transparently ignores nil values. When trying to access anything on a nil value, nil is returned:

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

iex> nil[:a]
nil

Internally, data[key] translates to Access.get(term, key, nil). Developers interested in implementing their own key-value data structures can implement the Access behaviour to provide the bracket-based access syntax. Access requires the key comparison to be implemented using the ===/2 operator.

Dot-based syntax

The data.field syntax is used exclusively to access atom fields in maps and structs. If the accessed field does not exist, an error is raised. This is a deliberate decision: since all of the fields in a struct are pre-determined, structs support only the dot-based syntax and not the access one.

Imagine a struct named User with a :name field. The following would raise:

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

Instead we should use the user.name syntax to access fields:

user.name
#=> "John"

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

Summing up

The bracket-based syntax, user[:name], is used by dynamic structures, is extensible and returns nil on missing keys.

The dot-based syntax, user.name, is used exclusively to access atom keys in maps and structs, and it raises on missing keys.

Nested data structures

Both key-based access syntaxes can be used with the nested update functions and macros in Kernel, such as Kernel.get_in/2, Kernel.put_in/3, Kernel.update_in/3, Kernel.pop_in/2, and Kernel.get_and_update_in/3.

For example, to update a map inside another map:

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

This module provides convenience functions for traversing other structures, like tuples and lists. These functions can be used in all the Access-related functions and macros in Kernel.

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

iex> languages = [
...>   %{name: "elixir", type: :functional},
...>   %{name: "c", type: :procedural}
...> ]
iex> user = %{name: "john", languages: languages}
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 some of the available accessors.

Link to this section Summary

Functions

Returns a function that accesses all the elements in a list.

Returns a function that accesses the element at index (zero based) of a list.

Returns a function that accesses the element at the given index in a tuple.

Fetches the value for the given key in a container (a map, keyword list, or struct that implements the Access behaviour).

Returns a function that accesses all elements of a list that match the provided predicate.

Gets the value for the given key in a container (a map, keyword list, or struct that implements the Access behaviour).

Gets and updates the given key in a container (a map, a keyword list, a struct that implements the Access behaviour).

Returns a function that accesses the given key in a map/struct.

Returns a function that accesses the given key in a map/struct.

Removes the entry with a given key from a container (a map, keyword list, or struct that implements the Access behaviour).

Callbacks

Invoked in order to access the value stored under key in the given term term.

Invoked in order to access the value under key and update it at the same time.

Invoked to "pop" the value under key out of the given data structure.

Link to this section Types

Link to this type

access_fun(data, get_value)

View Source

Specs

access_fun(data, get_value) ::
  get_fun(data, get_value) | get_and_update_fun(data, get_value)

Specs

any_container() :: any()

Specs

container() :: keyword() | struct() | map()
Link to this type

get_and_update_fun(data, get_value)

View Source

Specs

get_and_update_fun(data, get_value) ::
  (:get_and_update, data, (term() -> term()) ->
     {get_value, new_data :: container()} | :pop)
Link to this type

get_fun(data, get_value)

View Source

Specs

get_fun(data, get_value) ::
  (:get, data, (term() -> term()) -> {get_value, new_data :: container()})

Specs

key() :: any()

Specs

nil_container() :: nil

Specs

Specs

value() :: any()

Link to this section Functions

Specs

all() :: access_fun(data :: list(), get_value :: list())

Returns a function that accesses all the elements in a list.

The returned function is typically passed as an accessor to Kernel.get_in/2, Kernel.get_and_update_in/3, and friends.

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 multiplying 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: %{}

Specs

at(non_neg_integer()) :: access_fun(data :: list(), get_value :: term())

Returns a function that accesses the element at index (zero based) of a list.

The returned function is typically passed as an accessor to Kernel.get_in/2, Kernel.get_and_update_in/3, and friends.

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: %{}

Specs

elem(non_neg_integer()) :: access_fun(data :: tuple(), get_value :: term())

Returns a function that accesses the element at the given index in a tuple.

The returned function is typically passed as an accessor to Kernel.get_in/2, Kernel.get_and_update_in/3, and friends.

The returned function raises if index is out of bounds.

Note that popping elements out of tuples is not possible and raises an error.

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: %{}

Specs

fetch(container(), term()) :: {:ok, term()} | :error
fetch(nil_container(), any()) :: :error

Fetches the value for the given key in a container (a map, keyword list, or struct that implements the Access behaviour).

Returns {:ok, value} where value is the value under key if there is such a key, or :error if key is not found.

Examples

iex> Access.fetch(%{name: "meg", age: 26}, :name)
{:ok, "meg"}

iex> Access.fetch([ordered: true, on_timeout: :exit], :timeout)
:error
Link to this function

filter(func)

View Source (since 1.6.0)

Specs

filter((term() -> boolean())) :: access_fun(data :: list(), get_value :: list())

Returns a function that accesses all elements of a list that match the provided predicate.

The returned function is typically passed as an accessor to Kernel.get_in/2, Kernel.get_and_update_in/3, and friends.

Examples

iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
iex> get_in(list, [Access.filter(&(&1.salary > 20)), :name])
["francine"]
iex> get_and_update_in(list, [Access.filter(&(&1.salary <= 20)), :name], fn prev ->
...>   {prev, String.upcase(prev)}
...> end)
{["john"], [%{name: "JOHN", salary: 10}, %{name: "francine", salary: 30}]}

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

iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
iex> pop_in(list, [Access.filter(&(&1.salary >= 20))])
{[%{name: "francine", salary: 30}], [%{name: "john", salary: 10}]}
iex> pop_in(list, [Access.filter(&(&1.salary >= 20)), :name])
{["francine"], [%{name: "john", salary: 10}, %{salary: 30}]}

When no match is found, an empty list is returned and the update function is never called

iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
iex> get_in(list, [Access.filter(&(&1.salary >= 50)), :name])
[]
iex> get_and_update_in(list, [Access.filter(&(&1.salary >= 50)), :name], fn prev ->
...>   {prev, String.upcase(prev)}
...> end)
{[], [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]}

An error is raised if the predicate is not a function or is of the incorrect arity:

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

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

iex> get_in(%{}, [Access.filter(fn a -> a == 10 end)])
** (RuntimeError) Access.filter/1 expected a list, got: %{}
Link to this function

get(container, key, default \\ nil)

View Source

Specs

get(container(), term(), term()) :: term()
get(nil_container(), any(), default) :: default when default: var

Gets the value for the given key in a container (a map, keyword list, or struct that implements the Access behaviour).

Returns the value under key if there is such a key, or default if key is not found.

Examples

iex> Access.get(%{name: "john"}, :name, "default name")
"john"
iex> Access.get(%{name: "john"}, :age, 25)
25

iex> Access.get([ordered: true], :timeout)
nil
Link to this function

get_and_update(container, key, fun)

View Source

Specs

get_and_update(data, key(), (value() -> {get_value, value()} | :pop)) ::
  {get_value, data}
when data: container(), get_value: var

Gets and updates the given key in a container (a map, a keyword list, a struct that implements the Access behaviour).

The fun argument receives the value of key (or nil if key is not present in container) and must return a two-element tuple {get_value, update_value}: the "get" value get_value (the retrieved value, which can be operated on before being returned) and the new value to be stored under key (update_value). fun may also return :pop, which means the current value should be removed from the container and returned.

The returned value is a two-element tuple with the "get" value returned by fun and a new container with the updated value under key.

Link to this function

key(key, default \\ nil)

View Source

Specs

key(key(), term()) :: access_fun(data :: struct() | map(), get_value :: term())

Returns a function that accesses the given key in a map/struct.

The returned function is typically passed as an accessor to Kernel.get_in/2, Kernel.get_and_update_in/3, and friends.

The returned function uses the default value if the key does not exist. This can be used to specify defaults and safely traverse missing keys:

iex> get_in(%{}, [Access.key(:user, %{name: "meg"}), Access.key(:name)])
"meg"

Such is also useful when using update functions, allowing us to introduce values as we traverse the data structure for updates:

iex> put_in(%{}, [Access.key(:user, %{}), Access.key(:name)], "Mary")
%{user: %{name: "Mary"}}

Examples

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 or a struct:

iex> get_in(nil, [Access.key(:foo)])
** (BadMapError) expected a map, got: nil

iex> get_in([], [Access.key(:foo)])
** (BadMapError) expected a map, got: []

Specs

key!(key()) :: access_fun(data :: struct() | map(), get_value :: term())

Returns a function that accesses the given key in a map/struct.

The returned function is typically passed as an accessor to Kernel.get_in/2, Kernel.get_and_update_in/3, and friends.

Similar to key/2, but the returned function 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: []

Specs

pop(data, key()) :: {value(), data} when data: container()

Removes the entry with a given key from a container (a map, keyword list, or struct that implements the Access behaviour).

Returns a tuple containing the value associated with the key and the updated container. nil is returned for the value if the key isn't in the container.

Examples

With a map:

iex> Access.pop(%{name: "Elixir", creator: "Valim"}, :name)
{"Elixir", %{creator: "Valim"}}

A keyword list:

iex> Access.pop([name: "Elixir", creator: "Valim"], :name)
{"Elixir", [creator: "Valim"]}

An unknown key:

iex> Access.pop(%{name: "Elixir", creator: "Valim"}, :year)
{nil, %{creator: "Valim", name: "Elixir"}}

Link to this section Callbacks

Specs

fetch(term :: t(), key()) :: {:ok, value()} | :error

Invoked in order to access the value stored under key in the given term term.

This function should return {:ok, value} where value is the value under key if the key exists in the term, or :error if the key does not exist in the term.

Many of the functions defined in the Access module internally call this function. This function is also used when the square-brackets access syntax (structure[key]) is used: the fetch/2 callback implemented by the module that defines the structure struct is invoked and if it returns {:ok, value} then value is returned, or if it returns :error then nil is returned.

See the Map.fetch/2 and Keyword.fetch/2 implementations for examples of how to implement this callback.

Link to this callback

get_and_update(data, key, function)

View Source

Specs

get_and_update(data, key(), (value() -> {get_value, value()} | :pop)) ::
  {get_value, data}
when data: container() | any_container(), get_value: var

Invoked in order to access the value under key and update it at the same time.

The implementation of this callback should invoke fun with the value under key in the passed structure data, or with nil if key is not present in it. This function must return either {get_value, update_value} or :pop.

If the passed function returns {get_value, update_value}, the return value of this callback should be {get_value, new_data}, where:

  • get_value is the retrieved value (which can be operated on before being returned)

  • update_value is the new value to be stored under key

  • new_data is data after updating the value of key with update_value.

If the passed function returns :pop, the return value of this callback must be {value, new_data} where value is the value under key (or nil if not present) and new_data is data without key.

See the implementations of Map.get_and_update/3 or Keyword.get_and_update/3 for more examples.

Specs

pop(data, key()) :: {value(), data} when data: container() | any_container()

Invoked to "pop" the value under key out of the given data structure.

When key exists in the given structure data, the implementation should return a {value, new_data} tuple where value is the value that was under key and new_data is term without key.

When key is not present in the given structure, a tuple {value, data} should be returned, where value is implementation-defined.

See the implementations for Map.pop/3 or Keyword.pop/3 for more examples.