查看源代码 访问 行为 (Elixir v1.16.2)

基于键访问数据结构。

The Access 模块定义了一个行为,用于通过 data[key] 语法动态访问数据结构中任何类型的键。

Access 默认支持关键字列表 (Keyword) 和映射 (Map)。关键字只支持原子键,映射的键可以是任何类型。如果键不存在,两者都返回 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> keywords = [a: 1, b: 2]
iex> keywords[:c][:unknown]
nil

这是因为访问 nil 值上的任何内容都会返回 nil 本身

iex> nil[:a]
nil

访问语法还可以与 Kernel.put_in/2Kernel.update_in/2Kernel.get_and_update_in/2 宏一起使用,以允许在嵌套数据结构中设置值

iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in(users["john"][:age], 28)
%{"john" => %{age: 28}, "meg" => %{age: 23}}

映射和结构体

虽然访问语法在映射中通过 map[key] 是允许的,但如果你的映射是由预定义的原子键组成的,你应该优先使用 map.key 而不是 map[key] 来访问这些原子键,因为 map.key 会在键丢失时抛出异常(如果键是预定义的,这种情况不应该发生)。

类似地,由于结构体是映射,并且结构体具有预定义的键,它们只允许 struct.key 语法,并且不允许 struct[key] 访问语法。 Access.key/1 也可以用来构建对结构体和映射的动态访问。

简而言之,当使用 put_in/2 和它的朋友们时

put_in(struct_or_map.key, :value)
put_in(keyword_or_map[:key], :value)

当使用 put_in/3 和它的朋友们时

put_in(struct_or_map, [Access.key!(:key)], :value)
put_in(keyword_or_map, [:key], :value)

这涵盖了 Elixir 中映射的双重性质,因为它们可以是结构化数据或键值存储。有关更多信息,请参阅 Map 模块。

嵌套数据结构

两种基于键的访问语法都可以在 Kernel 中与嵌套更新函数和宏一起使用,例如 Kernel.get_in/2Kernel.put_in/3Kernel.update_in/3Kernel.pop_in/2Kernel.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}}

本模块为遍历其他结构(如元组和列表)提供了便利函数。这些函数可以在 Kernel 中所有与 Access 相关的函数和宏中使用。

例如,给定一个具有 :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/1key!/1elem/1all/0,了解一些可用的访问器。

摘要

回调

调用以访问给定项 term 中存储在 key 下的值。

调用以同时访问和更新 key 下的值。

调用以从给定的数据结构中“弹出” key 下的值。

函数

返回一个访问列表中所有元素的函数。

返回一个访问列表中 index(从零开始)处的元素的函数。

at/1 相同,但如果给定的索引超出范围,则会引发 Enum.OutOfBoundsError

返回一个访问元组中给定索引处的元素的函数。

获取容器(映射、关键字列表或实现 Access 行为的结构体)中给定键的值。

fetch/2 相同,但直接返回该值,或者如果未找到 key,则引发 KeyError 异常。

返回一个访问列表中所有与提供的谓词匹配的元素的函数。

获取容器(映射、关键字列表或实现 Access 行为的结构体)中给定键的值。

获取并更新 container(映射、关键字列表、实现 Access 行为的结构体)中的给定键。

返回一个访问映射/结构体中给定键的函数。

返回一个访问映射/结构体中给定键的函数。

从容器(映射、关键字列表或实现 Access 行为的结构体)中删除具有给定键的条目。

返回一个访问列表中所有位于提供的范围内的项目的函数。

类型

链接到此类型

access_fun(data, current_value)

查看源代码
@type access_fun(data, current_value) ::
  get_fun(data) | get_and_update_fun(data, current_value)
@type container() :: keyword() | struct() | map()
链接到此类型

get_and_update_fun(data, current_value)

查看源代码
@type get_and_update_fun(data, current_value) ::
  (:get_and_update, data, (term() -> term()) ->
     {current_value, new_data :: container()} | :pop)
@type get_fun(data) :: (:get, data, (term() -> term()) -> new_data :: container())
@type key() :: any()
@type nil_container() :: nil
@type t() :: container() | nil_container() | any()
@type value() :: any()

回调

@callback fetch(term :: t(), key()) :: {:ok, value()} | :error

调用以访问给定项 term 中存储在 key 下的值。

此函数应该返回 {:ok, value},其中 value 是项中 key 下的值(如果该键存在于项中),或者返回 :error(如果该键不存在于项中)。

Access 模块中定义的许多函数在内部调用此函数。当使用方括号访问语法 (structure[key]) 时,也会使用此函数:调用定义 structure 结构体的模块实现的 fetch/2 回调,如果它返回 {:ok, value},则返回 value,如果它返回 :error,则返回 nil

请参阅 Map.fetch/2Keyword.fetch/2 实现,了解如何实现此回调的示例。

链接到此回调

get_and_update(data, key, function)

查看源代码
@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()

调用以同时访问和更新 key 下的值。

此回调的实现应该使用传递的结构 datakey 下的值调用 fun,或者如果 key 不存在,则使用 nil 调用。此函数必须返回 {current_value, new_value}:pop

如果传递的函数返回 {current_value, new_value},则此回调的返回值应为 {current_value, new_data},其中

  • current_value 是检索的值(可以在返回之前对其进行操作)

  • new_value 是要存储在 key 下的新值

  • new_data 是在使用 new_value 更新 key 的值之后,data

如果传递的函数返回 :pop,则此回调的返回值必须为 {value, new_data},其中 valuekey 下的值(如果不存在,则为 nil),而 new_data 是不包含 keydata

请参阅 Map.get_and_update/3Keyword.get_and_update/3 的实现,了解更多示例。

@callback pop(data, key()) :: {value(), data} when data: container()

调用以从给定的数据结构中“弹出” key 下的值。

key 存在于给定的结构 data 中时,实现应该返回一个 {value, new_data} 元组,其中 value 是在 key 下的值,而 new_data 是不包含 keyterm

key 不存在于给定的结构中时,应该返回一个元组 {value, data},其中 value 是由实现定义的。

请参阅 Map.pop/3Keyword.pop/3 的实现,了解更多示例。

函数

@spec all() :: access_fun(data :: list(), current_value :: list())

返回一个访问列表中所有元素的函数。

返回的函数通常作为访问器传递给 Kernel.get_in/2Kernel.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: %{}
@spec at(integer()) :: access_fun(data :: list(), current_value :: term())

返回一个访问列表中 index(从零开始)处的元素的函数。

请记住,列表中的索引查找需要线性时间:列表越大,访问其索引所需的时间就越长。因此,通常会避免基于索引的操作,而倾向于使用 Enum 模块中的其他函数。

返回的函数通常作为访问器传递给 Kernel.get_in/2Kernel.get_and_update_in/3 及其朋友们。

示例

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 也可以用来从列表中弹出元素,或从列表内部弹出键

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)])
** (RuntimeError) Access.at/1 expected a list, got: %{}
链接到此函数

at!(index)

查看源代码 (自 1.11.0 起)
@spec at!(integer()) :: access_fun(data :: list(), current_value :: term())

at/1 相同,但如果给定的索引超出范围,则会引发 Enum.OutOfBoundsError

示例

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
@spec elem(non_neg_integer()) :: access_fun(data :: tuple(), current_value :: term())

返回一个访问元组中给定索引处的元素的函数。

返回的函数通常作为访问器传递给 Kernel.get_in/2Kernel.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: %{}
@spec fetch(container(), term()) :: {:ok, term()} | :error
@spec fetch(nil_container(), any()) :: :error

获取容器(映射、关键字列表或实现 Access 行为的结构体)中给定键的值。

返回 {:ok, value},其中 valuekey 下的值(如果存在这样的键),或者返回 :error(如果 key 未找到)。

示例

iex> Access.fetch(%{name: "meg", age: 26}, :name)
{:ok, "meg"}

iex> Access.fetch([ordered: true, on_timeout: :exit], :timeout)
:error
链接到此函数

fetch!(container, key)

查看源代码 (自 1.10.0 起)
@spec fetch!(container(), term()) :: term()

fetch/2 相同,但直接返回该值,或者如果未找到 key,则引发 KeyError 异常。

示例

iex> Access.fetch!(%{name: "meg", age: 26}, :name)
"meg"
链接到此函数

filter(func)

查看源代码 (自 1.6.0 起)
@spec filter((term() -> boolean())) ::
  access_fun(data :: list(), current_value :: list())

返回一个访问列表中所有与提供的谓词匹配的元素的函数。

返回的函数通常作为访问器传递给 Kernel.get_in/2Kernel.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)

查看源代码
@spec get(container(), term(), term()) :: term()
@spec 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)

查看源代码
@spec get_and_update(
  data,
  key(),
  (value() | nil -> {current_value, new_value :: value()} | :pop)
) ::
  {current_value, new_data :: data}
when data: container(), current_value: var

获取并更新 container(映射、关键字列表、实现 Access 行为的结构体)中的给定键。

fun 参数接收 key 的值(如果 key 不存在于 container 中,则为 nil),并且必须返回一个包含两个元素的元组 {current_value, new_value}:“获取”值 current_value(检索到的值,可以在返回之前对其进行操作)和要存储在 key 下的新值(new_value)。fun 也可以返回 :pop,这意味着当前值应该从容器中删除并返回。

返回值是一个包含两个元素的元组,其中包含 fun 返回的“获取”值以及一个在 key 下更新了值的新的容器。

示例

iex> Access.get_and_update([a: 1], :a, fn current_value ->
...>   {current_value, current_value + 1}
...> end)
{1, [a: 2]}
链接到此函数

key(key, default \\ nil)

查看源代码
@spec key(key(), term()) ::
  access_fun(data :: struct() | map(), current_value :: term())

返回一个访问映射/结构体中给定键的函数。

返回的函数通常作为访问器传递给 Kernel.get_in/2Kernel.get_and_update_in/3 及其朋友们。

如果键不存在,则返回的函数使用默认值。这可用于指定默认值并安全地遍历丢失的键

iex> get_in(%{}, [Access.key(:user, %{}), Access.key(:name, "meg")])
"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([], [Access.key(:foo)])
** (BadMapError) expected a map, got: []
@spec key!(key()) :: access_fun(data :: struct() | map(), current_value :: term())

返回一个访问映射/结构体中给定键的函数。

返回的函数通常作为访问器传递给 Kernel.get_in/2Kernel.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> 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"}}}

但是,无法使用点符号删除字段,因为这意味着这些字段也必须存在。无论如何,Access.key!/1 在事先不知道键并且必须动态访问键时非常有用。

如果访问的结构不是映射/结构,则会引发错误

iex> get_in([], [Access.key!(:foo)])
** (RuntimeError) Access.key!/1 expected a map/struct, got: []
@spec 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"}}
链接到此函数

slice(range)

查看源代码 (自 1.14 起)
@spec slice(Range.t()) :: access_fun(data :: list(), current_value :: list())

返回一个访问列表中所有位于提供的范围内的项目的函数。

范围将根据 Enum.slice/2 中的相同规则进行标准化。

返回的函数通常作为访问器传递给 Kernel.get_in/2Kernel.get_and_update_in/3 及其朋友们。

示例

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 也可以用于从列表中弹出元素或列表中的键

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}]}

如果没有找到匹配项,则返回一个空列表,并且永远不会调用更新函数

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}]}

如果访问的结构不是列表,则会引发错误

iex> get_in(%{}, [Access.slice(2..10//3)])
** (ArgumentError) Access.slice/1 expected a list, got: %{}

如果范围的步长为负,则会引发错误

iex> get_in([], [Access.slice(2..10//-1)])
** (ArgumentError) Access.slice/1 does not accept ranges with negative steps, got: 2..10//-1