Elixir v1.7.4 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 theKeyword
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 theMap
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 misisng 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
access_fun(data, get_value) :: get_fun(data, get_value) | get_and_update_fun(data, get_value)
Link to this section Functions
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: %{}
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: %{}
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: %{}
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
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: %{}
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
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
.
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: []
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: []
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
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.
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 underkey
new_data
isdata
after updating the value ofkey
withupdate_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.
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.