Elixir v1.2.6 Access behaviour

Key-based access to data structures via the foo[bar] 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.

Key-based 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.

Field-based lookups

The Access syntax (foo[bar]) cannot be used to access fields in structs. That’s by design, as Access is meant to be used for dynamic key-value structures, like maps and keywords, and not by static ones like structs.

However Elixir already provides a field-based lookup for structs. 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 cannot be extended by the developers, and will be always 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

Summary

Functions

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

Types

key()
key() :: any
t()
t() :: list | map | nil
value()
value() :: any

Functions

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, term, (term -> {get, term})) :: {get, t} when get: var

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

The argument function fun must receive the value for the given key (or nil if the key doesn’t exist in container). It must return a tuple containing the get value and the new value to be stored in the container.

This function returns a two-element tuple. The first element is the get value, as returned by fun. The second element is the container, updated with the value returned by fun.

Callbacks

fetch(t, key)
fetch(t, key) :: {:ok, value} | :error
get_and_update(t, key, list)
get_and_update(t, key, (value -> {value, value})) :: {value, t}