Spec-Zone.ru › Elixir 1.3

Доступ поведение

Базированный доступ к структурам данных с использованием синтаксиса data[key].

Elixir предоставляет два синтаксиса для доступа к значениям. user[:name] используется динамическими структурами, такими как карты и ключевые слова, в то время как user.name используется структурами. Основное различие заключается в том, что user[:name] не вызовет ошибку, если ключ :name отсутствует, но user.name вызовет ошибку, если ключа :name нет.

Помимо вышеперечисленных случаев, этот модуль предоставляет удобные функции для доступа к другим структурам, например, at/1 для списков и elem/1 для кортежей. Эти функции могут быть использованы вложенными функциями обновления в Kernel, такими как Kernel.get_in/2, Kernel.put_in/3, Kernel.update_in/3, Kernel.get_and_update_in/3 и другие.

Динамические запросы

Из коробки, Access работает с Keyword и 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 можно комбинировать с Kernel.put_in/3 для добавления значения в заданный ключ:

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

Этот синтаксис очень удобен, так как он может быть вложен произвольно:

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

Кроме того, Access прозрачно игнорирует nil значения:

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

Поскольку Access — это поведение, его можно реализовать для структур данных ключ-значение. Реализация должна быть добавлена в модуль, который определяет структуру, к которой осуществляется доступ. Access требует реализации сравнения ключей с использованием оператора ===.

Статические запросы

Синтаксис Access (foo[bar]) не может быть использован для доступа к полям в структурах, так как структуры по умолчанию не реализуют поведение Access. Это также дизайнерское решение: динамический поиск доступа предназначен для использования с динамическими структурами ключ-значение, такими как карты и ключевые слова, а не со статическими, такими как структуры.

Поэтому Elixir предоставляет статический поиск для полей карт и структур. Представьте структуру с именем User с полями name и age. Следующее приведет к ошибке:

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

Вместо этого структуры используют синтаксис user.name:

user.name
#=> "john"

Тот же синтаксис user.name также может быть использован функцией Kernel.put_in/2 для обновления полей структур:

put_in user.name, "mary"
%User{name: "mary"}

В отличие от user[:name], user.name не является расширяемым через поведение и ограничен только картами и структурами.

Подводя итоги:

  • user[:name] используется динамическими структурами, расширяем и не вызывает ошибку при отсутствии ключей
  • user.name используется статическими структурами, не расширяем и вызывает ошибку при отсутствии ключей

Доступные функции

Хотя Elixir предоставляет встроенный синтаксис только для обхода динамических и статических структур ключ-значение, этот модуль предоставляет удобные функции для обхода других структур, таких как кортежи и списки, для использования совместно с Kernel.put_in/2 в других.

Например, имея пользователя со списком языков, вот как глубоко пройтись по карте и преобразовать все имена языков в верхний регистр:

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}]}

См. функции key/1, key!/1, elem/1 и all/0 для текущих функций доступа.

Резюме

Типы

key()
t()
value()

Функции

all()

Доступ ко всем элементам в списке

at(индекс)

Доступ к элементу по индексу index (нумерация с нуля) в списке

elem(индекс)

Доступ к элементу по заданному индексу в кортеже

fetch(контейнер, ключ)

Извлечение значения контейнера для данного ключа

get(контейнер, ключ, значение по умолчанию \\ nil)

Получение значения контейнера для данного ключа

get_and_update(контейнер, ключ, функция)

Получение и обновление значения контейнера для данного ключа в один проход

key(ключ, значение по умолчанию \\ nil)

Доступ к заданному ключу в карте/структуре

key!(ключ)

Доступ к заданному ключу в карте/структуре

pop(контейнер, ключ)

Обработчики событий

fetch(t, ключ)
get(t, ключ, значение)
get_and_update(t, ключ, функция)
pop(t, ключ)

Типы

key()

key() :: any

t()

t() :: list | map | nil

value()

value() :: any

Функции

all()

Доступ ко всем элементам в списке.

Примеры

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"], [%{}, %{}]}

Вот пример, который проходит по списку, удаляя четные числа и умножая нечетные на 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]}

Если обращаются к структуре, которая не является списком, возникает ошибка:

iex> get_in(%{}, [Access.all()])
** (RuntimeError) Access.all/0 expected a list, got: %{}

at(индекс)

Доступ к элементу по индексу index (нумерация с нуля) в списке.

Примеры

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 также можно использовать для извлечения элементов из списка или ключей внутри списка:

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"}]}

Если индекс выходит за пределы, возвращается nil и функция обновления никогда не вызывается:

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"}]}

Для отрицательных индексов возникает ошибка:

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

Если обращаются к структуре, которая не является списком, возникает ошибка:

iex> get_in(%{}, [Access.at(1)])
** (RuntimeError) Access.at/1 expected a list, got: %{}

elem(индекс)

Доступ к элементу по заданному индексу в кортеже.

Возникает ошибка, если индекс выходит за пределы.

Примеры

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

Если обращаются к структуре, которая не является кортежем, возникает ошибка:

iex> get_in(%{}, [Access.elem(0)])
** (RuntimeError) Access.elem/1 expected a tuple, got: %{}

fetch(контейнер, ключ)

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

Извлечение значения контейнера для данного ключа.

get(контейнер, ключ, значение по умолчанию \\ nil)

get(t, term, term) :: term

Получение значения контейнера для данного ключа.

get_and_update(контейнер, ключ, функция)

get_and_update(t, key, (value -> {get, value})) :: {get, t} when get: var

Получение и обновление значения контейнера для данного ключа в один проход.

Этот fun аргумент получает значение key (или nil если key отсутствует) и должен вернуть кортеж из двух элементов: значение «получения» (извлеченное значение, которое можно обработать перед возвратом) и новое значение, которое нужно сохранить под key. fun также может вернуть :pop, что подразумевает, что текущее значение должно быть удалено из карты и возвращено.

Возвращаемое значение — это кортеж со значением «получения», возвращенным функцией fun, и новой картой с обновленным значением под ключом key.

key(ключ, значение по умолчанию \\ nil)

Доступ к заданному ключу в карте/структуре.

Использует значение по умолчанию, если ключ не существует или если обращаемое значение — 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: %{}}}

Возникает ошибка, если обращаются к структуре, которая не является картой/структурой/null:

iex> get_in([], [Access.key(:foo)])
** (RuntimeError) Access.key/1 expected a map/struct or nil, got: []

key!(ключ)

Доступ к заданному ключу в карте/структуре.

Возникает ошибка, если ключ не существует.

Примеры

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"}

Возникает ошибка, если обращаются к структуре, которая не является картой/структурой:

iex> get_in([], [Access.key!(:foo)])
** (RuntimeError) Access.key!/1 expected a map/struct, got: []

pop(контейнер, ключ)

Обработчики событий

fetch(t, ключ)

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

get(t, ключ, значение)

get(t, key, value) :: value

get_and_update(t, ключ, функция)

get_and_update(t, key, (value -> {value, value} | :pop)) :: {value, t}

pop(t, ключ)

pop(t, key) :: {value, t}

© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.3.4/Access.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API