Доступ поведение
Базированный на ключах доступ к структурам данных, использующий синтаксис 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]
"★☆" Обратите внимание, что синтаксис динамического поиска (term[key]) приблизительно соответствует Access.get(term, key, nil).
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. Следующее приведет к ошибке:
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 не расширяется через поведение и ограничен только структурами и ключами-атомами в картах.
Как упоминалось выше, это работает и для ключей-атомов в картах. Для получения дополнительной информации обратитесь к модулю Map.
Подводя итоги:
-
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 для некоторых доступных методов доступа.
Реализация поведения доступа для пользовательских структур данных
Чтобы использовать протокол Access с пользовательскими структурами данных (которые должны быть структурами), эти структуры должны реализовывать поведение Access. Например, для структуры User это необходимо сделать:
defmodule User do defstruct [:name, :email] @behaviour Access # Implementation of the Access callbacks... end
Резюме
Типы
- key()
- t()
- value()
Функции
- all()
-
Возвращает функцию, которая получает доступ ко всем элементам списка
- at(индекс)
-
Возвращает функцию, которая получает доступ к элементу по
index(нумерация с нуля) в списке - elem(индекс)
-
Возвращает функцию, которая получает доступ к элементу по указанному индексу в кортеже
- fetch(контейнер, ключ)
-
Извлекает значение для данного ключа в контейнере (карта, список ключевых слов или структура, реализующая поведение
Access) - get(контейнер, ключ, значение_по_умолчанию \\ nil)
-
Получает значение для данного ключа в контейнере (карта, список ключевых слов или структура, реализующая поведение
Access) - get_and_update(контейнер, ключ, функция)
-
Получает и обновляет заданный ключ в контейнере (карта, список ключевых слов или структура, реализующая поведение
Access) - key(ключ, значение_по_умолчанию)
-
Возвращает функцию, которая получает доступ к заданному ключу в карте/структуре
- key!(ключ)
-
Возвращает функцию, которая получает доступ к заданному ключу в карте/структуре
- pop(контейнер, ключ)
-
Удаляет запись с заданным ключом из контейнера (карта, список ключевых слов или структура, реализующая поведение
Access)
Обратные вызовы
- fetch(term, ключ)
-
Вызывается для доступа к значению, хранящемуся под
keyв заданном термеterm - get(term, ключ, значение_по_умолчанию)
-
Вызывается для доступа к значению, хранящемуся под
keyв заданном термеterm, с использованием значения по умолчаниюdefaultпри отсутствии - get_and_update(term, ключ, функция)
-
Вызывается для доступа к значению под
keyи одновременного его обновления - pop(term, ключ)
-
Вызывается для удаления значения под
keyиз данного терма
Типы
key()
key() :: any()
t()
t() :: list() | map() | nil | any()
value()
value() :: any()
Функции
all()
Возвращает функцию, которая получает доступ ко всем элементам в списке.
Возвращаемая функция обычно передаётся в качестве метода доступа к 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 (нумерация с нуля) в списке.
Возвращаемая функция обычно передаётся в качестве метода доступа к 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(индекс)
Возвращает функцию, которая получает доступ к элементу по заданному индексу в кортеже.
Возвращаемая функция обычно передаётся в качестве метода доступа к Kernel.get_in/2, Kernel.get_and_update_in/3 и им подобным.
Генерирует ошибку, если индекс выходит за пределы границ.
Примеры
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 Извлекает значение для данного ключа в контейнере (карта, список ключевых слов или структура, реализующая поведение Access).
get(контейнер, ключ, значение_по_умолчанию \\ nil)
get(t(), term(), term()) :: term()
Получает значение для заданного ключа в контейнере (словарь, список ключевых слов или структура, реализующая поведение Access).
get_and_update(container, key, fun)
get_and_update(container :: t(), key(), (value() -> {get_value, update_value} | :pop)) :: {get_value, container :: t()} when update_value: value(), get_value: var Получает и обновляет заданный ключ в контейнере (словарь, список ключевых слов или структура, реализующая поведение Access).
Этот fun аргумент получает значение key (или nil если key отсутствует) и должен вернуть кортеж из двух элементов: значение «get» (извлечённое значение, с которым можно выполнить операции перед возвратом) и новое значение, которое должно быть сохранено под key. fun также может вернуть :pop, что подразумевает удаление текущего значения из контейнера и его возврат.
Возвращаемое значение — это кортеж из двух элементов: значение «get», возвращённое fun, и новый контейнер с обновлённым значением под key.
key(key, default)
Возвращает функцию, которая обращается к заданному ключу в словаре/структуре.
Возвращаемая функция обычно передаётся в качестве доступа к Kernel.get_in/2, Kernel.get_and_update_in/3 и т.п.
Возвращаемая функция использует значение по умолчанию, если ключ не существует. Это можно использовать для указания значений по умолчанию и безопасного прохождения по отсутствующим ключам:
iex> get_in(%{}, [Access.key(:user, %{}), Access.key(:name, nil)])
nil Это также полезно при использовании функций обновления, позволяя нам вводить значения по мере прохождения по структуре данных для обновлений:
iex> put_in(%{}, [Access.key(:user, %{}), Access.key(:name, nil)], "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, nil)]) ** (BadMapError) expected a map, got: nil iex> get_in([], [Access.key(:foo, nil)]) ** (BadMapError) expected a map, got: []
key!(key)
Возвращает функцию, которая обращается к заданному ключу в словаре/структуре.
Возвращаемая функция обычно передаётся в качестве доступа к Kernel.get_in/2, Kernel.get_and_update_in/3 и т.п.
Возникает исключение, если ключ не существует.
Примеры
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(container, key)
Удаляет запись с заданным ключом из контейнера (словарь, список ключевых слов или структура, реализующая поведение 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(term, key, default)
get(term :: t(), key(), default :: value()) :: value()
Вызывается для доступа к значению, хранящемуся под key в заданном элементе term, по умолчанию default при отсутствии.
Эта функция должна вернуть значение под ключом key в term, если такой ключ существует, в противном случае default.
Для большинства структур данных это можно реализовать, используя fetch/2 внутренним образом; например:
def get(structure, key, default) do
case fetch(structure, key) do
{:ok, value} -> value
:error -> default
end
end См. реализации Map.get/3 и Keyword.get/3 для дополнительных примеров.
get_and_update(term, key, function)
get_and_update(term :: t(), key(), (value() -> {value(), value()} | :pop)) :: {value(), t()} Вызывается для доступа к значению под key и одновременного обновления.
Реализация этого обратного вызова должна вызвать переданную функцию со значением под ключом key в переданной структуре или nil если ключ отсутствует. Эта функция должна вернуть либо {value_to_return, new_value}, либо :pop.
Если она возвращает {value_to_return, new_value}, значение этого обратного вызова должно быть {value_to_return, new_term}, где new_term — term после обновления значения key значением new_value.
Если она возвращает :pop, значение этого обратного вызова должно быть {value,
new_term}, где value — значение под key или nil при отсутствии, а new_term — term без ключа key.
См. реализации Map.get_and_update/3 или Keyword.get_and_update/3 для дополнительных примеров.
pop(term, key)
pop(term :: t(), key()) :: {value(), t()} Вызывается для «удаления» значения под key из заданного элемента.
Когда ключ key существует в заданном term, реализация должна вернуть кортеж {value, new_term}, где value — значение, которое было под key, и new_term — term без key.
Когда ключ key отсутствует в заданном term, должен быть возвращён кортеж {value, term}, где value — определённое реализацией значение.
См. реализации Map.pop/3 или Keyword.pop/3 для дополнительных примеров.
© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.4.5/Access.html