Spec-Zone.ru › Elixir 1.6

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

Базовый доступ к структурам данных с использованием синтаксиса 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 (data[key]) нельзя использовать для доступа к полям в структурах, так как структуры по умолчанию не реализуют поведение 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 в других случаях.

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

Чтобы использовать поведение Access с пользовательскими структурами данных (которые должны быть структурами), эти структуры должны реализовать поведение Access. Например, для структуры User это должно быть сделано так:

defmodule User do
  defstruct [:name, :email]

  @behaviour Access
  # Implementation of the Access callbacks...
end

Резюме

Типы

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, default \\ nil)

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

key!(key)

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

pop(container, key)

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

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

fetch(term, key)

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

get(term, key, default)

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

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 не найден.

filter(func)

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 не найден.

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

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

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

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

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, %{}), Access.key(:name)])
nil

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

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

key!(key)

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

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

Возвращаемая функция обычно передаётся в качестве аксессора к 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)

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(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(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.6.6/Access.html

Spec-Zone.ru

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