Spec-Zone.ru › Elixir 1.8

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

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

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

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

key!(key)

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

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(container(), term()) :: {:ok, term()} | :error
fetch(nil_container(), any()) :: :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)

(с версии 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(container(), term(), term()) :: term()
get(nil_container(), any(), default) :: default when default: var

Получает значение для данного ключа в контейнере (отображение, список ключевых слов или структура, реализующая поведение 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

Получает и обновляет заданный ключ в 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, %{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: []

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

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

Spec-Zone.ru

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