Spec-Zone.ru › Elixir 1.7

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

Доступ к структурам данных по ключам.

Elixir поддерживает три основных конструкции ключ-значение: ключевые слова, карты и структуры. Он также поддерживает два механизма доступа к этим ключам: с помощью скобок (через data[key]) и с помощью синтаксиса точки (через data.field).

В следующем разделе мы кратко повторим конструкции ключ-значение, а затем обсудим механизмы доступа.

Конструкции ключ-значение

Elixir предоставляет три основные конструкции ключ-значение, которые суммированы ниже:

  • списки ключевых слов — это списки кортежей из двух элементов, где первый элемент — атом. Обычно записываются в синтаксисе [key: value], они поддерживают только атомные ключи. Списки ключевых слов используются почти исключительно для передачи опций функциям и макросам. Они сохраняют порядок пользователя и допускают дублирование ключей. См. модуль Keyword.

  • карты — это основная структура данных ключ-значение в Elixir. Они способны поддерживать миллиарды ключей любого типа. Они записываются с помощью синтаксиса %{key => value}, а также поддерживают синтаксис %{key: value}, когда ключи являются атомами. Они не имеют определенного порядка и не допускают дублирования ключей. См. модуль Map.

  • структуры — это именованные карты с предопределенным набором ключей. Они определяются с помощью defstruct/1 и записываются с помощью синтаксиса %StructName{key: value}.

Доступ по ключу

Elixir предоставляет два механизма доступа к структурам данных по ключу, описанных ниже.

Доступ по скобкам

Синтаксис data[key] используется для доступа к структурам данных с динамическим числом ключей, таким как ключевые слова и карты. Ключ может быть любого типа. Синтаксис доступа по скобкам возвращает nil, если ключ не существует:

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]
"★☆"

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

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

Кроме того, синтаксис доступа по скобкам прозрачно игнорирует значения nil. При попытке доступа к чему-либо по значению nil, возвращается nil.

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

iex> nil[:a]
nil

Внутренне, data[key] переводится в Access.get(term, key, nil). Разработчики, заинтересованные в реализации собственных структур данных ключ-значение, могут реализовать поведение Access для предоставления синтаксиса доступа по скобкам. Access требует реализации сравнения ключей с помощью оператора ===/2.

Синтаксис с точкой

Синтаксис data.field используется исключительно для доступа к атомным полям в картах и структурах. Если доступное поле не существует, генерируется ошибка. Это преднамеренное решение: поскольку все поля в структуре предопределены, структуры поддерживают только синтаксис с точкой, а не синтаксис доступа.

Представьте структуру с именем User с полем :name. Следующее вызовет ошибку:

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], user.name не расширяется с помощью поведения и ограничен только структурами и атомными ключами в картах.

Заключение

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

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

Вложенные структуры данных

Оба синтаксиса доступа по ключу могут использоваться с функциями и макросами вложенного обновления в Kernel, таких как Kernel.get_in/2, Kernel.put_in/3, Kernel.update_in/3, Kernel.pop_in/2 и Kernel.get_and_update_in/3.

Например, чтобы обновить карту внутри другой карты:

iex> users = %{“john” => %{age: 27}, “meg” => %{age: 23}} iex> put_in(users[“john”].age, 28) %{“john” => %{age: 28}, “meg” => %{age: 23}}

Этот модуль предоставляет удобные функции для обхода других структур, таких как кортежи и списки. Эти функции могут использоваться во всех функциях и макросах, связанных с Access, в Kernel.

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

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

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

Обзор

Типы

access_fun(data, get_value)
any_container()
container()
get_and_update_fun(data, get_value)
get_fun(data, get_value)
key()
nil_container()
t()
value()

Функции

all()

Возвращает функцию, которая получает доступ ко всем элементам в списке

at(index)

Возвращает функцию, которая получает доступ к элементу по индексу index (с нуля) в списке

elem(index)

Возвращает функцию, которая получает доступ к элементу по заданному индексу в кортеже

fetch(container, key)

Извлекает значение для заданного ключа в контейнере (карта, список ключевых слов или структура, реализующие поведение Access)

filter(func)

Возвращает функцию, которая получает доступ ко всем элементам списка, соответствующим заданному предикату

get(container, key, default \\ nil)

Возвращает значение для заданного ключа в контейнере (карта, список ключевых слов или структура, реализующие поведение Access)

get_and_update(container, key, fun)

Получает и обновляет заданный ключ в container (карта, список ключевых слов, структура, реализующие поведение Access)

key!(key)

Возвращает функцию, которая получает доступ к заданному ключу в карте/структуре

key(key, default \\ nil)

Возвращает функцию, которая получает доступ к заданному ключу в карте/структуре

pop(container, key)

Удаляет запись с заданным ключом из контейнера (карта, список ключевых слов или структура, реализующие поведение Access)

Обратные вызовы

fetch(term, key)

Вызывается для доступа к значению, хранящемуся по key в данном термине term

get_and_update(data, key, function)

Вызывается для доступа к значению по key и одновременного обновления

pop(data, key)

Вызывается для «извлечения» значения по key из данной структуры данных

Типы

access_fun(data, get_value)

access_fun(data, get_value) ::
  get_fun(data, get_value) | get_and_update_fun(data, get_value)

any_container()

any_container() :: any()

container()

container() :: keyword() | struct() | map()

get_and_update_fun(data, get_value)

get_and_update_fun(data, get_value) ::
  (:get_and_update, data, (term() -> term()) ->
     {get_value, new_data :: container()} | :pop)

get_fun(data, get_value)

get_fun(data, get_value) ::
  (:get, data, (term() -> term()) -> {get_value, new_data :: container()})

key()

key() :: any()

nil_container()

nil_container() :: nil

t()

t() :: container() | nil_container() | any_container()

value()

value() :: any()

Функции

all()

all() :: access_fun(data :: list(), get_value :: list())

Возвращает функцию, которая получает доступ ко всем элементам в списке.

Возвращаемая функция обычно передается как функция доступа к Kernel.get_in/2, Kernel.get_and_update_in/3 и аналогичным функциям.

Примеры

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)

at(non_neg_integer()) :: access_fun(data :: list(), get_value :: term())

Возвращает функцию, которая получает доступ к элементу по индексу index (с нуля) в списке.

Возвращаемая функция обычно передается в качестве аксессора к Kernel.get_in/2, Kernel.get_and_update_in/3 и подобным функциям.

Примеры

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(index)

elem(non_neg_integer()) :: access_fun(data :: tuple(), get_value :: term())

Возвращает функцию, которая обращается к элементу по заданному индексу в кортеже.

Возвращаемая функция обычно передается в качестве аксессора к Kernel.get_in/2, Kernel.get_and_update_in/3 и подобным функциям.

Возвращаемая функция вызывает исключение, если index находится за пределами границ.

Обратите внимание, что извлечение элементов из кортежей невозможно и вызывает ошибку.

Примеры

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(container, key)

fetch(nil_container(), any()) :: :error
fetch(container(), term()) :: {:ok, term()} | :error

Извлекает значение для данного ключа в контейнере (словарь, список ключевых слов или структура, которая реализует поведение Access).

Возвращает {:ok, value}, где value — значение по ключу key, если такой ключ существует, или :error, если ключ key не найден.

Примеры

iex> Access.fetch(%{name: "meg", age: 26}, :name)
{:ok, "meg"}

iex> Access.fetch([ordered: true, on_timeout: :exit], :timeout)
:error

filter(func) (since 1.6.0)

filter((term() -> boolean())) :: access_fun(data :: list(), get_value :: list())

Возвращает функцию, которая обращается ко всем элементам списка, соответствующим предоставленному предикату.

Возвращаемая функция обычно передается в качестве аксессора к Kernel.get_in/2, Kernel.get_and_update_in/3 и подобным функциям.

Примеры

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

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

Если совпадений не найдено, возвращается пустой список, и функция обновления не вызывается.

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

Ошибка возникает, если предикат не является функцией или имеет неправильную арность:

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

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

iex> get_in(%{}, [Access.filter(fn a -> a == 10 end)])
** (RuntimeError) Access.filter/1 expected a list, got: %{}

get(container, key, default \\ nil)

get(nil_container(), any(), default) :: default when default: var
get(container(), term(), term()) :: term()

Получает значение для заданного ключа в контейнере (словарь, список ключевых слов или структура, которая реализует поведение Access).

Возвращает значение по ключу key, если такой ключ существует, или default, если ключ key не найден.

Примеры

iex> Access.get(%{name: "john"}, :name, "default name")
"john"
iex> Access.get(%{name: "john"}, :age, 25)
25

iex> Access.get([ordered: true], :timeout)
nil

get_and_update(container, key, fun)

get_and_update(data, key(), (value() -> {get_value, value()} | :pop)) ::
  {get_value, data}
when data: container(), get_value: var

Получает и обновляет заданный ключ в контейнере (словаре, списке ключевых слов, структуре, которая реализует поведение Access).

Аргумент fun получает значение key (или nil, если key отсутствует в container). Он должен вернуть кортеж из двух элементов {get_value, update_value}: значение «получить» get_value (полученное значение, которое можно обработать перед возвращением) и новое значение для хранения по ключу key (update_value). fun также может вернуть :pop, что означает, что текущее значение должно быть удалено из контейнера и возвращено.

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

key!(key)

key!(key()) :: access_fun(data :: struct() | map(), get_value :: term())

Возвращает функцию, которая обращается к заданному ключу в словаре/структуре.

Возвращаемая функция обычно передается в качестве аксессора к Kernel.get_in/2, Kernel.get_and_update_in/3 и подобным функциям.

Аналогично key/2, но возвращаемая функция вызывает исключение, если ключ не существует.

Примеры

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: []

key(key, default \\ nil)

key(key(), term()) ::
  access_fun(data :: struct() | map(), get_value :: term())

Возвращает функцию, которая обращается к заданному ключу в словаре/структуре.

Возвращаемая функция обычно передается в качестве аксессора к Kernel.get_in/2, Kernel.get_and_update_in/3 и подобным функциям.

Возвращаемая функция использует значение по умолчанию, если ключ не существует. Это позволяет задавать значения по умолчанию и безопасно обрабатывать отсутствующие ключи:

iex> get_in(%{}, [Access.key(:user, %{name: "meg"}), Access.key(:name)])
"meg"

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

iex> put_in(%{}, [Access.key(:user, %{}), Access.key(:name)], "Mary")
%{user: %{name: "Mary"}}

Примеры

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: %{}}}

Ошибка возникает, если доступная структура не является словарем или структурой:

iex> get_in(nil, [Access.key(:foo)])
** (BadMapError) expected a map, got: nil

iex> get_in([], [Access.key(:foo)])
** (BadMapError) expected a map, got: []

pop(container, key)

pop(data, key()) :: {value(), data} when data: container()

Удаляет запись с заданным ключом из контейнера (словаря, списка ключевых слов или структуры, которая реализует поведение Access).

Возвращает кортеж, содержащий значение, связанное с ключом, и обновлённый контейнер. nil возвращается для значения, если ключ отсутствует в контейнере.

Примеры

Для словаря:

iex> Access.pop(%{name: "Elixir", creator: "Valim"}, :name)
{"Elixir", %{creator: "Valim"}}

Для списка ключевых слов:

iex> Access.pop([name: "Elixir", creator: "Valim"], :name)
{"Elixir", [creator: "Valim"]}

Для неизвестного ключа:

iex> Access.pop(%{name: "Elixir", creator: "Valim"}, :year)
{nil, %{creator: "Valim", name: "Elixir"}}

Обратные вызовы

fetch(term, key)

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

Вызывается для доступа к значению, хранящемуся под key в заданном выражении term.

Эта функция должна вернуть {:ok, value}, где value — значение под ключом key, если ключ существует в выражении, или :error, если ключ в выражении отсутствует.

Многие функции, определенные в модуле Access, внутренне вызывают эту функцию. Эта функция также используется при использовании синтаксиса доступа в квадратных скобках (structure[key]). Вызывается обратный вызов fetch/2, реализованный модулем, который определяет структуру structure. Если он возвращает {:ok, value}, возвращается value, или если он возвращает :error, возвращается nil.

См. реализации Map.fetch/2 и Keyword.fetch/2 для примеров реализации этого обратного вызова.

get_and_update(data, key, function)

get_and_update(data, key(), (value() -> {get_value, value()} | :pop)) ::
  {get_value, data}
when data: container() | any_container(), get_value: var

Вызывается для доступа к значению под key и одновременного обновления.

Реализация этого обратного вызова должна вызвать fun со значением под key в переданной структуре data, или со значением nil, если key отсутствует в ней. Эта функция должна вернуть либо {get_value, update_value}, либо :pop.

Если переданная функция возвращает {get_value, update_value}, возвращаемое значение этого обратного вызова должно быть {get_value, new_data}, где:

  • get_value — полученное значение (которое можно обработать перед возвращением)

  • update_value — новое значение для хранения под key

  • new_data — data после обновления значения key значением update_value.

Если переданная функция возвращает :pop, возвращаемое значение этого обратного вызова должно быть {value, new_data}, где value — значение под key (или nil, если оно отсутствует), а new_data — data без key.

См. реализации Map.get_and_update/3 или Keyword.get_and_update/3 для дополнительных примеров.

pop(data, key)

pop(data, key()) :: {value(), data} when data: container() | any_container()

Вызывается для «извлечения» значения под key из заданной структуры данных.

Если key существует в заданной структуре data, реализация должна вернуть кортеж {value, new_data}, где value — значение, которое было под key, а new_data — term без key.

Если key отсутствует в заданной структуре, должен быть возвращён кортеж {value, data}, где value — значение, определяемое реализацией.

См. реализации Map.pop/3 или Keyword.pop/3 для дополнительных примеров.

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

Spec-Zone.ru

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