Access behaviour
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.
Summary
Types
Functions
- all()
-
Accesses all the elements in a list
- at(index)
-
Accesses the element at
index
(zero based) of a list - elem(index)
-
Accesses the element at the given index in a tuple
- fetch(container, key)
-
Fetches the container’s value for the given key
- get(container, key, default \\ nil)
-
Gets the container’s value for the given key
- get_and_update(container, key, fun)
-
Gets and updates the container’s value for the given key, in a single pass
- key(key, default \\ nil)
-
Accesses the given key in a map/struct
- key!(key)
-
Accesses the given key in a map/struct
- pop(container, key)
Callbacks
Types
key()
key() :: any
t()
t() :: list | map | nil
value()
value() :: any
Functions
all()
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: %{}
at(index)
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: %{}
elem(index)
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: %{}
fetch(container, key)
fetch(t, term) :: {:ok, term} | :error
Fetches the container’s value for the given key.
get(container, key, default \\ nil)
get(t, term, term) :: term
Gets the container’s value for the given key.
get_and_update(container, key, fun)
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
.
key(key, default \\ nil)
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: []
key!(key)
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: []
pop(container, key)
Callbacks
fetch(t, key)
fetch(t, key) :: {:ok, value} | :error
get(t, key, value)
get(t, key, value) :: value
get_and_update(t, key, function)
get_and_update(t, key, (value -> {value, value} | :pop)) :: {value, t}
pop(t, key)
pop(t, key) :: {value, t}
© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.3.4/Access.html