Files
n8n-openai-adapter/lib/elixir/lib/access.ex
T

1249 lines
42 KiB
Elixir

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Access do
@moduledoc """
Key-based access to data structures.
The `Access` module defines a behaviour for dynamically accessing
keys of any type in a data structure via the `data[key]` syntax.
`Access` supports keyword lists (`Keyword`) and maps (`Map`) out
of the box. Keyword lists support only atom keys, while keys for maps
can be of any type. Both return `nil` if the key does not exist:
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]
"★☆"
This syntax is very convenient as it can be nested arbitrarily:
iex> keywords = [a: 1, b: 2]
iex> keywords[:c][:unknown]
nil
This works because accessing anything on a `nil` value returns
`nil` itself:
iex> nil[:a]
nil
## Maps and structs
While the access syntax is allowed in maps via `map[key]`,
if your map is made of predefined atom keys, you should prefer
to access those atom keys with `map.key` instead of `map[key]`,
as `map.key` will raise if the key is missing (which is not
supposed to happen if the keys are predefined) or if `map` is
`nil`.
Similarly, since structs are maps and structs have predefined
keys, they only allow the `struct.key` syntax and they do not
allow the `struct[key]` access syntax.
In other words, the `map[key]` syntax is loose, returning `nil`
for missing keys, while the `map.key` syntax is strict, raising
for both nil values and missing keys.
To bridge this gap, Elixir provides the `get_in/1` and `get_in/2`
functions, which are capable of traversing nested data structures,
even in the presence of `nil`s:
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_in(users["john"].age)
27
iex> get_in(users["unknown"].age)
nil
Notice how, even if no user was found, `get_in/1` returned `nil`.
Outside of `get_in/1`, trying to access the field `.age` on `nil`
would raise.
The `get_in/2` function takes one step further by allowing
different accessors to be mixed in. For example, given a user
map with the `:name` and `:languages` keys, here is how to
access the name of all programming languages:
iex> languages = [
...> %{name: "elixir", type: :functional},
...> %{name: "c", type: :procedural}
...> ]
iex> user = %{name: "john", languages: languages}
iex> get_in(user, [:languages, Access.all(), :name])
["elixir", "c"]
This module provides convenience functions for traversing other
structures, like tuples and lists. As we will see next, they can
even be used to update nested data structures.
If you want to learn more about the dual nature of maps in Elixir,
as they can be either for structured data or as a key-value store,
see the `Map` module.
## Updating nested data structures
The access syntax can also be used with the `Kernel.put_in/2`,
`Kernel.update_in/2`, `Kernel.get_and_update_in/2`, and `Kernel.pop_in/1`
macros to further manipulate values in nested data structures:
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users["john"].age, 28)
%{"john" => %{age: 28}, "meg" => %{age: 23}}
As shown in the previous section, you can also use the
`Kernel.put_in/3`, `Kernel.update_in/3`, `Kernel.pop_in/2`, and
`Kernel.get_and_update_in/3` functions to provide nested
custom accessors. For instance, given a user map with the
`:name` and `:languages` keys, here is how to deeply traverse
the map and convert all language names to uppercase:
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}
]
}
See the functions `key/1`, `key!/1`, `elem/1`, and `all/0` for
some of the available accessors.
"""
@type container :: keyword | struct | map
@type nil_container :: nil
@type t :: container | nil_container | any
@type key :: any
@type value :: any
@type get_fun(data) ::
(:get, data, (term -> term) -> new_data :: container)
@type get_and_update_fun(data, current_value) ::
(:get_and_update, data, (term -> term) ->
{current_value, new_data :: container} | :pop)
@type access_fun(data, current_value) ::
get_fun(data) | get_and_update_fun(data, current_value)
@doc """
Invoked in order to access the value stored under `key` in the given term `term`.
This function should return `{:ok, value}` where `value` is the value under
`key` if the key exists in the term, or `:error` if the key does not exist in
the term.
Many of the functions defined in the `Access` module internally call this
function. This function is also used when the square-brackets access syntax
(`structure[key]`) is used: the `fetch/2` callback implemented by the module
that defines the `structure` struct is invoked and if it returns `{:ok,
value}` then `value` is returned, or if it returns `:error` then `nil` is
returned.
See the `Map.fetch/2` and `Keyword.fetch/2` implementations for examples of
how to implement this callback.
"""
@callback fetch(term :: t, key) :: {:ok, value} | :error
@doc """
Invoked in order to access the value under `key` and update it at the same time.
The implementation of this callback should invoke `fun` with the value under
`key` in the passed structure `data`, or with `nil` if `key` is not present in it.
This function must return either `{current_value, new_value}` or `:pop`.
If the passed function returns `{current_value, new_value}`,
the return value of this callback should be `{current_value, new_data}`, where:
* `current_value` is the retrieved value (which can be operated on before being returned)
* `new_value` is the new value to be stored under `key`
* `new_data` is `data` after updating the value of `key` with `new_value`.
If the passed function returns `:pop`, the return value of this callback
must be `{value, new_data}` where `value` is the value under `key`
(or `nil` if not present) and `new_data` is `data` without `key`.
See the implementations of `Map.get_and_update/3` or `Keyword.get_and_update/3`
for more examples.
"""
@callback get_and_update(data, key, (value | nil -> {current_value, new_value :: value} | :pop)) ::
{current_value, new_data :: data}
when current_value: value, data: container
@doc """
Invoked to "pop" the value under `key` out of the given data structure.
When `key` exists in the given structure `data`, the implementation should
return a `{value, new_data}` tuple where `value` is the value that was under
`key` and `new_data` is `term` without `key`.
When `key` is not present in the given structure, a tuple `{value, data}`
should be returned, where `value` is implementation-defined.
See the implementations for `Map.pop/3` or `Keyword.pop/3` for more examples.
"""
@callback pop(data, key) :: {value, data} when data: container
defmacrop raise_undefined_behaviour(exception, module, top) do
quote do
exception =
case __STACKTRACE__ do
[unquote(top) | _] ->
reason =
"""
#{inspect(unquote(module))} does not implement the Access behaviour
You can use the "struct.field" syntax to access struct fields. \
You can also use Access.key!/1 to access struct fields dynamically \
inside get_in/put_in/update_in\
"""
%{unquote(exception) | reason: reason}
_ ->
unquote(exception)
end
reraise exception, __STACKTRACE__
end
end
defguardp is_probably_keyword(list) when list == [] or is_atom(elem(hd(list), 0))
@doc """
Fetches the value for the given key in a container (a map, keyword
list, or struct that implements the `Access` behaviour).
Returns `{:ok, value}` where `value` is the value under `key` if there is such
a key, or `:error` if `key` is not found.
## Examples
iex> Access.fetch(%{name: "meg", age: 26}, :name)
{:ok, "meg"}
iex> Access.fetch([ordered: true, on_timeout: :exit], :timeout)
:error
"""
@spec fetch(container, term) :: {:ok, term} | :error
@spec fetch(nil_container, any) :: :error
def fetch(container, key)
def fetch(%module{} = container, key) do
module.fetch(container, key)
rescue
exception in UndefinedFunctionError ->
raise_undefined_behaviour(exception, module, {^module, :fetch, [^container, ^key], _})
end
def fetch(map, key) when is_map(map) do
case map do
%{^key => value} -> {:ok, value}
_ -> :error
end
end
def fetch(list, key) when is_list(list) and is_atom(key) do
case :lists.keyfind(key, 1, list) do
{_, value} -> {:ok, value}
false -> :error
end
end
def fetch(list, key) when is_list(list) do
raise ArgumentError,
"the Access calls for keywords expect the key to be an atom, got: " <> inspect(key)
end
def fetch(nil, _key) do
:error
end
@doc """
Same as `fetch/2` but returns the value directly,
or raises a `KeyError` exception if `key` is not found.
## Examples
iex> Access.fetch!(%{name: "meg", age: 26}, :name)
"meg"
"""
@doc since: "1.10.0"
@spec fetch!(container, term) :: term
def fetch!(container, key) do
case fetch(container, key) do
{:ok, value} -> value
:error -> raise(KeyError, key: key, term: container)
end
end
@doc """
Gets the value for the given key in a container (a map, keyword
list, or struct that implements the `Access` behaviour).
Returns the value under `key` if there is such a key, or `default` if `key` is
not found.
## Examples
iex> Access.get(%{name: "john"}, :name, "default name")
"john"
iex> Access.get(%{name: "john"}, :age, 25)
25
iex> Access.get([ordered: true], :timeout)
nil
"""
@spec get(container, term, term) :: term
@spec get(nil_container, any, default) :: default when default: var
def get(container, key, default \\ nil)
# Reimplementing the same logic as Access.fetch/2 here is done for performance, since
# this is called a lot and calling fetch/2 means introducing some overhead (like
# building the "{:ok, _}" tuple and deconstructing it back right away).
def get(%module{} = container, key, default) do
try do
module.fetch(container, key)
rescue
exception in UndefinedFunctionError ->
raise_undefined_behaviour(exception, module, {^module, :fetch, [^container, ^key], _})
else
{:ok, value} -> value
:error -> default
end
end
def get(map, key, default) when is_map(map) do
case map do
%{^key => value} -> value
_ -> default
end
end
def get(list, key, default) when is_list(list) and is_atom(key) do
case :lists.keyfind(key, 1, list) do
{_, value} -> value
false -> default
end
end
def get(list, key, _default) when is_list(list) and is_integer(key) do
raise ArgumentError, """
the Access module does not support accessing lists by index, got: #{inspect(key)}
Accessing a list by index is typically discouraged in Elixir, \
instead we prefer to use the Enum module to manipulate lists \
as a whole. If you really must access a list element by index, \
you can use Enum.at/2 or the functions in the List module\
"""
end
def get(list, key, _default) when is_list(list) do
raise ArgumentError, """
the Access module supports only keyword lists (with atom keys), got: #{inspect(key)}
If you want to search lists of tuples, use List.keyfind/3\
"""
end
def get(nil, _key, default) do
default
end
@doc """
Gets and updates the given key in a `container` (a map, a keyword list,
a struct that implements the `Access` behaviour).
The `fun` argument receives the value of `key` (or `nil` if `key` is not
present in `container`) and must return a two-element tuple `{current_value, new_value}`:
the "get" value `current_value` (the retrieved value, which can be operated on before
being returned) and the new value to be stored under `key` (`new_value`).
`fun` may also return `:pop`, which means the current value
should be removed from the container and returned.
The returned value is a two-element tuple with the "get" value returned by
`fun` and a new container with the updated value under `key`.
## Examples
iex> Access.get_and_update([a: 1], :a, fn current_value ->
...> {current_value, current_value + 1}
...> end)
{1, [a: 2]}
"""
@spec get_and_update(data, key, (value | nil -> {current_value, new_value :: value} | :pop)) ::
{current_value, new_data :: data}
when current_value: var, data: container
def get_and_update(container, key, fun)
def get_and_update(%module{} = container, key, fun) do
module.get_and_update(container, key, fun)
rescue
exception in UndefinedFunctionError ->
raise_undefined_behaviour(
exception,
module,
{^module, :get_and_update, [^container, ^key, ^fun], _}
)
end
def get_and_update(map, key, fun) when is_map(map) do
Map.get_and_update(map, key, fun)
end
def get_and_update(list, key, fun) when is_list(list) and is_atom(key) do
Keyword.get_and_update(list, key, fun)
end
def get_and_update(list, key, _fun) when is_list(list) and is_integer(key) do
raise ArgumentError, """
the Access module does not support accessing lists by index, got: #{inspect(key)}
Accessing a list by index is typically discouraged in Elixir, \
instead we prefer to use the Enum module to manipulate lists \
as a whole. If you really must modify a list element by index, \
you can use Access.at/1 or the functions in the List module\
"""
end
def get_and_update(list, key, _fun) when is_list(list) do
raise ArgumentError,
"the Access module supports only keyword lists (with atom keys), got: " <> inspect(key)
end
def get_and_update(nil, key, _fun) do
raise ArgumentError, "could not put/update key #{inspect(key)} on a nil value"
end
@doc """
Removes the entry with a given key from a container (a map, keyword
list, or struct that implements the `Access` behaviour).
Returns a tuple containing the value associated with the key and the
updated container. `nil` is returned for the value if the key isn't
in the container.
## Examples
With a map:
iex> Access.pop(%{name: "Elixir", creator: "Valim"}, :name)
{"Elixir", %{creator: "Valim"}}
A keyword list:
iex> Access.pop([name: "Elixir", creator: "Valim"], :name)
{"Elixir", [creator: "Valim"]}
An unknown key:
iex> Access.pop(%{name: "Elixir", creator: "Valim"}, :year)
{nil, %{creator: "Valim", name: "Elixir"}}
"""
@spec pop(data, key) :: {value, data} when data: container
def pop(%module{} = container, key) do
module.pop(container, key)
rescue
exception in UndefinedFunctionError ->
raise_undefined_behaviour(exception, module, {^module, :pop, [^container, ^key], _})
end
def pop(map, key) when is_map(map) do
Map.pop(map, key)
end
def pop(list, key) when is_list(list) do
Keyword.pop(list, key)
end
def pop(nil, key) do
raise ArgumentError, "could not pop key #{inspect(key)} on a nil value"
end
## Accessors
@doc """
Returns a function that accesses the given key in a map/struct/keyword list.
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
`Kernel.get_and_update_in/3`, and friends.
The returned function uses the default value if the key does not exist.
This can be used to specify defaults and safely traverse missing keys:
iex> get_in(%{}, [Access.key(:user, %{}), Access.key(:name, "meg")])
"meg"
Such is also useful when using update functions, allowing us to introduce
values as we traverse the data structure for updates:
iex> put_in(%{}, [Access.key(:user, %{}), Access.key(:name)], "Mary")
%{user: %{name: "Mary"}}
## Examples
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> keyword = [user: [name: "john"]]
iex> get_in(keyword, [Access.key(:unknown, []), Access.key(:name, "john")])
"john"
iex> get_and_update_in(keyword, [Access.key(:user), Access.key(:name)], fn prev ->
...> {prev, String.upcase(prev)}
...> end)
{"john", [user: [name: "JOHN"]]}
iex> pop_in(keyword, [Access.key(:user), Access.key(:name)])
{"john", [user: []]}
An error is raised if the accessed structure is not a map, struct, or keyword list:
iex> get_in(123, [Access.key(:foo)])
** (RuntimeError) Access.key/2 expected a map/struct/keyword list, got: ...
iex> put_in([1, 2, 3], [Access.key(:foo)], :bar)
** (RuntimeError) Access.key/2 expected a map/struct/keyword list, got: ...
"""
@spec key(key, term) :: access_fun(data :: struct | map | keyword, current_value :: term)
def key(key, default \\ nil) do
fn
:get, %{} = data, next ->
next.(Map.get(data, key, default))
:get_and_update, %{} = data, next ->
value = Map.get(data, key, default)
case next.(value) do
{get, update} -> {get, Map.put(data, key, update)}
:pop -> {value, Map.delete(data, key)}
end
:get, data, next when is_probably_keyword(data) ->
next.(Keyword.get(data, key, default))
:get_and_update, data, next when is_probably_keyword(data) ->
value = Keyword.get(data, key, default)
case next.(value) do
{get, update} -> {get, Keyword.put(data, key, update)}
:pop -> {value, Keyword.delete(data, key)}
end
_op, data, _next ->
raise "Access.key/2 expected a map/struct/keyword list, got: #{inspect(data)}"
end
end
@doc """
Returns a function that accesses the given key in a map/struct/keyword list.
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
`Kernel.get_and_update_in/3`, and friends.
Similar to `key/2`, but the returned function raises if the key does not exist.
## Examples
iex> keyword = [user: [name: "john"]]
iex> get_in(keyword, [Access.key!(:user), Access.key!(:name)])
"john"
iex> get_and_update_in(keyword, [Access.key!(:user), Access.key!(:name)], fn prev ->
...> {prev, String.upcase(prev)}
...> end)
{"john", [user: [name: "JOHN"]]}
iex> pop_in(keyword, [Access.key!(:user), Access.key!(:name)])
{"john", [user: []]}
iex> get_in(keyword, [Access.key!(:user), Access.key!(:unknown)])
** (KeyError) key :unknown not found in:
...
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:
...
The examples above could be partially written as:
iex> map = %{user: %{name: "john"}}
iex> map.user.name
"john"
iex> get_and_update_in(map.user.name, fn prev ->
...> {prev, String.upcase(prev)}
...> end)
{"john", %{user: %{name: "JOHN"}}}
However, it is not possible to remove fields using the dot notation,
as it is implied those fields must also be present. In any case,
`Access.key!/1` is useful when the key is not known in advance
and must be accessed dynamically.
An error is raised if the accessed structure is not a map/struct/keyword list:
iex> get_in(123, [Access.key!(:foo)])
** (RuntimeError) Access.key!/1 expected a map/struct/keyword list, got: 123
iex> put_in([1, 2, 3], [Access.key!(:foo)], :bar)
** (RuntimeError) Access.key!/1 expected a map/struct/keyword list, got: ...
"""
@spec key!(key) :: access_fun(data :: struct | map | keyword, current_value :: term)
def key!(key) do
fn
:get, %{} = data, next ->
next.(Map.fetch!(data, key))
:get_and_update, %{} = data, next ->
value = Map.fetch!(data, key)
case next.(value) do
{get, update} -> {get, Map.put(data, key, update)}
:pop -> {value, Map.delete(data, key)}
end
:get, data, next when is_probably_keyword(data) ->
next.(Keyword.fetch!(data, key))
:get_and_update, data, next when is_probably_keyword(data) ->
value = Keyword.fetch!(data, key)
case next.(value) do
{get, update} -> {get, Keyword.put(data, key, update)}
:pop -> {value, Keyword.delete(data, key)}
end
_op, data, _next ->
raise "Access.key!/1 expected a map/struct/keyword list, got: #{inspect(data)}"
end
end
@doc ~S"""
Returns a function that accesses the element at the given index in a tuple.
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
`Kernel.get_and_update_in/3`, and friends.
The returned function raises if `index` is out of bounds.
Note that popping elements out of tuples is not possible and raises an
error.
## Examples
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
An error is raised if the accessed structure is not a tuple:
iex> get_in(%{}, [Access.elem(0)])
** (RuntimeError) Access.elem/1 expected a tuple, got: %{}
"""
@spec elem(non_neg_integer) :: access_fun(data :: tuple, current_value :: term)
def elem(index) when is_integer(index) and index >= 0 do
pos = index + 1
fn
:get, data, next when is_tuple(data) ->
next.(:erlang.element(pos, data))
:get_and_update, data, next when is_tuple(data) ->
value = :erlang.element(pos, data)
case next.(value) do
{get, update} -> {get, :erlang.setelement(pos, data, update)}
:pop -> raise "cannot pop data from a tuple"
end
_op, data, _next ->
raise "Access.elem/1 expected a tuple, got: #{inspect(data)}"
end
end
@doc ~S"""
Returns a function that accesses all the elements in a list.
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
`Kernel.get_and_update_in/3`, and friends.
## Examples
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"], [%{}, %{}]}
Here is an example that traverses the list dropping even
numbers and multiplying odd numbers by 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]}
An error is raised if the accessed structure is not a list:
iex> get_in(%{}, [Access.all()])
** (RuntimeError) Access.all/0 expected a list, got: %{}
"""
@spec all() :: access_fun(data :: list, current_value :: list)
def all() do
&all/3
end
defp all(:get, data, next) when is_list(data) do
Enum.map(data, next)
end
defp all(:get_and_update, data, next) when is_list(data) do
all(data, next, _gets = [], _updates = [])
end
defp all(_op, data, _next) do
raise "Access.all/0 expected a list, got: #{inspect(data)}"
end
defp all([head | rest], next, gets, updates) do
case next.(head) do
{get, update} -> all(rest, next, [get | gets], [update | updates])
:pop -> all(rest, next, [head | gets], updates)
end
end
defp all([], _next, gets, updates) do
{:lists.reverse(gets), :lists.reverse(updates)}
end
@doc ~S"""
Returns a function that accesses the element at `index` (zero based) of a list.
Keep in mind that index lookups in lists take linear time: the larger the list,
the longer it will take to access its index. Therefore index-based operations
are generally avoided in favor of other functions in the `Enum` module.
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
`Kernel.get_and_update_in/3`, and friends.
## Examples
iex> list = [%{name: "john"}, %{name: "mary"}]
iex> get_in(list, [Access.at(1), :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"}]}
iex> get_and_update_in(list, [Access.at(-1), :name], fn prev ->
...> {prev, String.upcase(prev)}
...> end)
{"mary", [%{name: "john"}, %{name: "MARY"}]}
`at/1` can also be used to pop elements out of a list or
a key inside of a list:
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"}]}
When the index is out of bounds, `nil` is returned and the update function is never called:
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"}]}
An error is raised if the accessed structure is not a list:
iex> get_in(%{}, [Access.at(1)])
** (RuntimeError) Access.at/1 expected a list, got: %{}
"""
@spec at(integer) :: access_fun(data :: list, current_value :: term)
def at(index) when is_integer(index) do
fn op, data, next -> at(op, data, index, next) end
end
defp at(:get, data, index, next) when is_list(data) do
data |> Enum.at(index) |> next.()
end
defp at(:get_and_update, data, index, next) when is_list(data) do
get_and_update_at(data, index, next, [], fn -> nil end)
end
defp at(_op, data, _index, _next) do
raise "Access.at/1 expected a list, got: #{inspect(data)}"
end
defp get_and_update_at([head | rest], 0, next, updates, _default_fun) do
case next.(head) do
{get, update} -> {get, :lists.reverse([update | updates], rest)}
:pop -> {head, :lists.reverse(updates, rest)}
end
end
defp get_and_update_at([_ | _] = list, index, next, updates, default_fun) when index < 0 do
list_length = length(list)
if list_length + index >= 0 do
get_and_update_at(list, list_length + index, next, updates, default_fun)
else
{default_fun.(), list}
end
end
defp get_and_update_at([head | rest], index, next, updates, default_fun) when index > 0 do
get_and_update_at(rest, index - 1, next, [head | updates], default_fun)
end
defp get_and_update_at([], _index, _next, updates, default_fun) do
{default_fun.(), :lists.reverse(updates)}
end
@doc ~S"""
Same as `at/1` except that it raises `Enum.OutOfBoundsError`
if the given index is out of bounds.
## Examples
iex> get_in([:a, :b, :c], [Access.at!(2)])
:c
iex> get_in([:a, :b, :c], [Access.at!(3)])
** (Enum.OutOfBoundsError) out of bounds error at position 3 when traversing enumerable [:a, :b, :c]
"""
@doc since: "1.11.0"
@spec at!(integer) :: access_fun(data :: list, current_value :: term)
def at!(index) when is_integer(index) do
fn op, data, next -> at!(op, data, index, next) end
end
defp at!(:get, data, index, next) when is_list(data) do
case Enum.fetch(data, index) do
{:ok, value} -> next.(value)
:error -> raise Enum.OutOfBoundsError, index: index, enumerable: data
end
end
defp at!(:get_and_update, data, index, next) when is_list(data) do
get_and_update_at(data, index, next, [], fn ->
raise Enum.OutOfBoundsError, index: index, enumerable: data
end)
end
defp at!(_op, data, _index, _next) do
raise "Access.at!/1 expected a list, got: #{inspect(data)}"
end
@doc ~S"""
Returns a function that accesses all elements of a list that match the provided predicate.
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
`Kernel.get_and_update_in/3`, and friends.
## Examples
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` can also be used to pop elements out of a list or
a key inside of a list:
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}]}
When no match is found, an empty list is returned and the update function is never called:
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}]}
An error is raised if the accessed structure is not a list:
iex> get_in(%{}, [Access.filter(fn a -> a == 10 end)])
** (RuntimeError) Access.filter/1 expected a list, got: %{}
"""
@doc since: "1.6.0"
@spec filter((term -> as_boolean(term))) :: access_fun(data :: list, current_value :: list)
def filter(func) when is_function(func) do
fn op, data, next -> filter(op, data, func, next) end
end
defp filter(:get, data, func, next) when is_list(data) do
for elem <- data, func.(elem), do: next.(elem)
end
defp filter(:get_and_update, data, func, next) when is_list(data) do
get_and_update_filter(data, func, next, [], [])
end
defp filter(_op, data, _func, _next) do
raise "Access.filter/1 expected a list, got: #{inspect(data)}"
end
defp get_and_update_filter([head | rest], func, next, updates, gets) do
if func.(head) do
case next.(head) do
{get, update} ->
get_and_update_filter(rest, func, next, [update | updates], [get | gets])
:pop ->
get_and_update_filter(rest, func, next, updates, [head | gets])
end
else
get_and_update_filter(rest, func, next, [head | updates], gets)
end
end
defp get_and_update_filter([], _func, _next, updates, gets) do
{:lists.reverse(gets), :lists.reverse(updates)}
end
@doc ~S"""
Returns a function that accesses all items of a list that are within the provided range.
The range will be normalized following the same rules from `Enum.slice/2`.
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
`Kernel.get_and_update_in/3`, and friends.
## Examples
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}, %{name: "vitor", salary: 25}]
iex> get_in(list, [Access.slice(1..2), :name])
["francine", "vitor"]
iex> get_and_update_in(list, [Access.slice(1..3//2), :name], fn prev ->
...> {prev, String.upcase(prev)}
...> end)
{["francine"], [%{name: "john", salary: 10}, %{name: "FRANCINE", salary: 30}, %{name: "vitor", salary: 25}]}
`slice/1` can also be used to pop elements out of a list or
a key inside of a list:
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}, %{name: "vitor", salary: 25}]
iex> pop_in(list, [Access.slice(-2..-1)])
{[%{name: "francine", salary: 30}, %{name: "vitor", salary: 25}], [%{name: "john", salary: 10}]}
iex> pop_in(list, [Access.slice(-2..-1), :name])
{["francine", "vitor"], [%{name: "john", salary: 10}, %{salary: 30}, %{salary: 25}]}
When no match is found, an empty list is returned and the update function is never called
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}, %{name: "vitor", salary: 25}]
iex> get_in(list, [Access.slice(5..10//2), :name])
[]
iex> get_and_update_in(list, [Access.slice(5..10//2), :name], fn prev ->
...> {prev, String.upcase(prev)}
...> end)
{[], [%{name: "john", salary: 10}, %{name: "francine", salary: 30}, %{name: "vitor", salary: 25}]}
An error is raised if the accessed structure is not a list:
iex> get_in(%{}, [Access.slice(2..10//3)])
** (ArgumentError) Access.slice/1 expected a list, got: %{}
An error is raised if the step of the range is negative:
iex> get_in([], [Access.slice(2..10//-1)])
** (ArgumentError) Access.slice/1 does not accept ranges with negative steps, got: 2..10//-1
"""
@doc since: "1.14"
@spec slice(Range.t()) :: access_fun(data :: list, current_value :: list)
def slice(%Range{} = range) do
if range.step > 0 do
fn op, data, next -> slice(op, data, range, next) end
else
raise ArgumentError,
"Access.slice/1 does not accept ranges with negative steps, got: #{inspect(range)}"
end
end
defp slice(:get, data, %Range{} = range, next) when is_list(data) do
data
|> Enum.slice(range)
|> Enum.map(next)
end
defp slice(:get_and_update, data, range, next) when is_list(data) do
range = normalize_range(range, data)
if range.first > range.last do
{[], data}
else
get_and_update_slice(data, range, next, [], [], 0)
end
end
defp slice(_op, data, _range, _next) do
raise ArgumentError, "Access.slice/1 expected a list, got: #{inspect(data)}"
end
@doc """
Returns a function that accesses all values in a map or a keyword list.
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
`Kernel.get_and_update_in/3`, and friends.
## Examples
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_in(users, [Access.values(), :age]) |> Enum.sort()
[23, 27]
iex> update_in(users, [Access.values(), :age], fn age -> age + 1 end)
%{"john" => %{age: 28}, "meg" => %{age: 24}}
iex> put_in(users, [Access.values(), :planet], "Earth")
%{"john" => %{age: 27, planet: "Earth"}, "meg" => %{age: 23, planet: "Earth"}}
Values in keyword lists can be accessed as well:
iex> users = [john: %{age: 27}, meg: %{age: 23}]
iex> get_and_update_in(users, [Access.values(), :age], fn age -> {age, age + 1} end)
{[27, 23], [john: %{age: 28}, meg: %{age: 24}]}
By returning `:pop` from an accessor function, you can remove the accessed key and value
from the map or keyword list:
iex> require Integer
iex> numbers = [one: 1, two: 2, three: 3, four: 4]
iex> get_and_update_in(numbers, [Access.values()], fn num ->
...> if Integer.is_even(num), do: :pop, else: {num, to_string(num)}
...> end)
{[1, 2, 3, 4], [one: "1", three: "3"]}
An error is raised if the accessed structure is not a map nor a keyword list:
iex> get_in([1, 2, 3], [Access.values()])
** (RuntimeError) Access.values/0 expected a map or a keyword list, got: [1, 2, 3]
"""
@doc since: "1.19.0"
@spec values() :: Access.access_fun(data :: map() | keyword(), current_value :: list())
def values do
&values/3
end
defp values(:get, data = %{}, next) do
Enum.map(data, fn {_key, value} -> next.(value) end)
end
defp values(:get_and_update, data = %{}, next) do
{reverse_gets, updated_data} =
Enum.reduce(data, {[], %{}}, fn {key, value}, {gets, data_acc} ->
case next.(value) do
{get, update} -> {[get | gets], Map.put(data_acc, key, update)}
:pop -> {[value | gets], data_acc}
end
end)
{Enum.reverse(reverse_gets), updated_data}
end
defp values(op, data = [], next) do
values_keyword(op, data, next)
end
defp values(op, data = [{key, _value} | _tail], next) when is_atom(key) do
values_keyword(op, data, next)
end
defp values(_op, data, _next) do
raise "Access.values/0 expected a map or a keyword list, got: #{inspect(data)}"
end
defp values_keyword(:get, data, next) do
Enum.map(data, fn {key, value} when is_atom(key) -> next.(value) end)
end
defp values_keyword(:get_and_update, data, next) do
{reverse_gets, reverse_updated_data} =
Enum.reduce(data, {[], []}, fn {key, value}, {gets, data_acc} when is_atom(key) ->
case next.(value) do
{get, update} -> {[get | gets], [{key, update} | data_acc]}
:pop -> {[value | gets], data_acc}
end
end)
{Enum.reverse(reverse_gets), Enum.reverse(reverse_updated_data)}
end
defp normalize_range(%Range{first: first, last: last, step: step}, list)
when first < 0 or last < 0 do
count = length(list)
first = if first >= 0, do: first, else: Kernel.max(first + count, 0)
last = if last >= 0, do: last, else: last + count
Range.new(first, last, step)
end
defp normalize_range(range, _list), do: range
defp get_and_update_slice(rest, %Range{last: last}, _next, updates, gets, index)
when index > last do
{:lists.reverse(gets), :lists.reverse(updates, rest)}
end
defp get_and_update_slice([head | rest], range, next, updates, gets, index) do
if index in range do
case next.(head) do
:pop ->
get_and_update_slice(rest, range, next, updates, [head | gets], index + 1)
{get, update} ->
get_and_update_slice(
rest,
range,
next,
[update | updates],
[get | gets],
index + 1
)
end
else
get_and_update_slice(rest, range, next, [head | updates], gets, index + 1)
end
end
defp get_and_update_slice([], _range, _next, updates, gets, _index) do
{:lists.reverse(gets), :lists.reverse(updates)}
end
@doc ~S"""
Returns a function that accesses the first element of a list that matches the provided predicate.
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
`Kernel.get_and_update_in/3`, and friends.
## Examples
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
iex> get_in(list, [Access.find(&(&1.salary > 20)), :name])
"francine"
iex> get_and_update_in(list, [Access.find(&(&1.salary <= 40)), :name], fn prev ->
...> {prev, String.upcase(prev)}
...> end)
{"john", [%{name: "JOHN", salary: 10}, %{name: "francine", salary: 30}]}
`find/1` can also be used to pop the first found element out of a list or
a key inside of a list:
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
iex> pop_in(list, [Access.find(&(&1.salary <= 40))])
{%{name: "john", salary: 10}, [%{name: "francine", salary: 30}]}
When no match is found, nil is returned and the update function is never called:
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
iex> get_in(list, [Access.find(&(&1.salary >= 50)), :name])
nil
iex> get_and_update_in(list, [Access.find(&(&1.salary >= 50)), :name], fn prev ->
...> {prev, String.upcase(prev)}
...> end)
{nil, [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]}
An error is raised if the accessed structure is not a list:
iex> get_in(%{}, [Access.find(fn a -> a == 10 end)])
** (RuntimeError) Access.find/1 expected a list, got: %{}
"""
@doc since: "1.17.0"
@spec find((term -> as_boolean(term))) :: access_fun(data :: list, current_value :: term)
def find(predicate) when is_function(predicate, 1) do
fn op, data, next -> find(op, data, predicate, next) end
end
defp find(:get, data, predicate, next) when is_list(data) do
data |> Enum.find(predicate) |> next.()
end
defp find(:get_and_update, data, predicate, next) when is_list(data) do
get_and_update_find(data, [], predicate, next)
end
defp find(_op, data, _predicate, _next) do
raise "Access.find/1 expected a list, got: #{inspect(data)}"
end
defp get_and_update_find([], updates, _predicate, _next) do
{nil, :lists.reverse(updates)}
end
defp get_and_update_find([head | rest], updates, predicate, next) do
if predicate.(head) do
case next.(head) do
{get, update} -> {get, :lists.reverse([update | updates], rest)}
:pop -> {head, :lists.reverse(updates, rest)}
end
else
get_and_update_find(rest, [head | updates], predicate, next)
end
end
end