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.
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.
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 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.
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
key() :: any
t() :: list | map | nil
value() :: any
Accesses all the elements in a list.
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.
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.
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(t, term) :: {:ok, term} | :error
Fetches the container’s value for the given key.
get(t, term, term) :: term
Gets the container’s value for the given key.
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
.
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
.
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.
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: []
fetch(t, key) :: {:ok, value} | :error
get(t, key, value) :: value
get_and_update(t, key, (value -> {value, value} | :pop)) :: {value, t}
pop(t, key) :: {value, t}
© 2012–2017 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.3.3/Access.html