Доступ поведение
Доступ к структурам данных по ключам.
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