查看源代码 访问 行为 (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/2
、Kernel.update_in/2
和 Kernel.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/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}}
本模块为遍历其他结构(如元组和列表)提供了便利函数。这些函数可以在 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}
]
}
摘要
函数
返回一个访问列表中所有元素的函数。
返回一个访问列表中 index
(从零开始)处的元素的函数。
与 at/1
相同,但如果给定的索引超出范围,则会引发 Enum.OutOfBoundsError
。
返回一个访问元组中给定索引处的元素的函数。
获取容器(映射、关键字列表或实现 Access
行为的结构体)中给定键的值。
返回一个访问列表中所有与提供的谓词匹配的元素的函数。
获取容器(映射、关键字列表或实现 Access
行为的结构体)中给定键的值。
获取并更新 container
(映射、关键字列表、实现 Access
行为的结构体)中的给定键。
返回一个访问映射/结构体中给定键的函数。
返回一个访问映射/结构体中给定键的函数。
从容器(映射、关键字列表或实现 Access
行为的结构体)中删除具有给定键的条目。
返回一个访问列表中所有位于提供的范围内的项目的函数。
类型
@type access_fun(data, current_value) :: get_fun(data) | get_and_update_fun(data, current_value)
@type key() :: any()
@type nil_container() :: nil
@type t() :: container() | nil_container() | any()
@type value() :: any()
回调
调用以访问给定项 term
中存储在 key
下的值。
此函数应该返回 {:ok, value}
,其中 value
是项中 key
下的值(如果该键存在于项中),或者返回 :error
(如果该键不存在于项中)。
Access
模块中定义的许多函数在内部调用此函数。当使用方括号访问语法 (structure[key]
) 时,也会使用此函数:调用定义 structure
结构体的模块实现的 fetch/2
回调,如果它返回 {:ok, value}
,则返回 value
,如果它返回 :error
,则返回 nil
。
请参阅 Map.fetch/2
和 Keyword.fetch/2
实现,了解如何实现此回调的示例。
@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
下的值。
此回调的实现应该使用传递的结构 data
中 key
下的值调用 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}
,其中 value
是 key
下的值(如果不存在,则为 nil
),而 new_data
是不包含 key
的 data
。
请参阅 Map.get_and_update/3
或 Keyword.get_and_update/3
的实现,了解更多示例。
调用以从给定的数据结构中“弹出” key
下的值。
当 key
存在于给定的结构 data
中时,实现应该返回一个 {value, new_data}
元组,其中 value
是在 key
下的值,而 new_data
是不包含 key
的 term
。
当 key
不存在于给定的结构中时,应该返回一个元组 {value, data}
,其中 value
是由实现定义的。
请参阅 Map.pop/3
或 Keyword.pop/3
的实现,了解更多示例。
函数
@spec all() :: access_fun(data :: list(), current_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: %{}
@spec at(integer()) :: access_fun(data :: list(), current_value :: term())
返回一个访问列表中 index
(从零开始)处的元素的函数。
请记住,列表中的索引查找需要线性时间:列表越大,访问其索引所需的时间就越长。因此,通常会避免基于索引的操作,而倾向于使用 Enum
模块中的其他函数。
返回的函数通常作为访问器传递给 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_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: %{}
@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/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: %{}
@spec fetch(container(), term()) :: {:ok, term()} | :error
@spec 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
与 fetch/2
相同,但直接返回该值,或者如果未找到 key
,则引发 KeyError
异常。
示例
iex> Access.fetch!(%{name: "meg", age: 26}, :name)
"meg"
@spec filter((term() -> boolean())) :: access_fun(data :: list(), current_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: %{}
@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
@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]}
返回一个访问映射/结构体中给定键的函数。
返回的函数通常作为访问器传递给 Kernel.get_in/2
、Kernel.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/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> 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: []
从容器(映射、关键字列表或实现 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"}}
@spec slice(Range.t()) :: access_fun(data :: list(), current_value :: list())
返回一个访问列表中所有位于提供的范围内的项目的函数。
范围将根据 Enum.slice/2
中的相同规则进行标准化。
返回的函数通常作为访问器传递给 Kernel.get_in/2
、Kernel.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