Skip to content

Commit 68a60eb

Browse files
committed
Add Access.values/0 and Access.keys/0
1 parent b08eea7 commit 68a60eb

File tree

1 file changed

+118
-0
lines changed

1 file changed

+118
-0
lines changed

lib/elixir/lib/access.ex

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,124 @@ defmodule Access do
999999
raise ArgumentError, "Access.slice/1 expected a list, got: #{inspect(data)}"
10001000
end
10011001

1002+
@doc """
1003+
Returns a function that accesses all values in a map.
1004+
1005+
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
1006+
`Kernel.get_and_update_in/3`, and friends.
1007+
1008+
See `keys/0` for a function that accesses all keys in a map.
1009+
1010+
## Examples
1011+
1012+
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
1013+
iex> get_in(users, [Access.values(), :age])
1014+
[27, 23]
1015+
iex> update_in(users, [Access.values(), :age], fn age -> age + 1 end)
1016+
%{"john" => %{age: 28}, "meg" => %{age: 24}}
1017+
iex> put_in(users, [Access.values(), :planet], "Earth")
1018+
%{"john" => %{age: 27, planet: "Earth"}, "meg" => %{age: 23, planet: "Earth"}}
1019+
1020+
By returning `:pop` from an accessor function, you can remove the accessed key and value
1021+
from the map:
1022+
1023+
iex> require Integer
1024+
iex> numbers = %{one: 1, two: 2, three: 3, four: 4}
1025+
iex> get_and_update_in(numbers, [Access.values()], fn num ->
1026+
...> if Integer.is_even(num), do: :pop, else: {num, to_string(num)}
1027+
...> end)
1028+
{[1, 2, 3, 4], %{one: "1", three: "3"}}
1029+
1030+
An error is raised if the accessed structure is not a map:
1031+
1032+
iex> get_in([1, 2, 3], [Access.values()])
1033+
** (RuntimeError) Access.values/0 expected a map, got: [1, 2, 3]
1034+
"""
1035+
@spec values() :: Access.access_fun(data :: map(), current_value :: list())
1036+
def values do
1037+
&values/3
1038+
end
1039+
1040+
defp values(:get, data = %{}, next) do
1041+
Enum.map(data, fn {_key, value} -> next.(value) end)
1042+
end
1043+
1044+
defp values(:get_and_update, data = %{}, next) do
1045+
{reverse_gets, updated_data} =
1046+
Enum.reduce(data, {[], %{}}, fn {key, value}, {gets, data_acc} ->
1047+
case next.(value) do
1048+
{get, update} -> {[get | gets], Map.put(data_acc, key, update)}
1049+
:pop -> {[value | gets], data_acc}
1050+
end
1051+
end)
1052+
1053+
{Enum.reverse(reverse_gets), updated_data}
1054+
end
1055+
1056+
defp values(_op, data, _next) do
1057+
raise "Access.values/0 expected a map, got: #{inspect(data)}"
1058+
end
1059+
1060+
@doc """
1061+
Returns a function that accesses all keys in a map.
1062+
1063+
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
1064+
`Kernel.get_and_update_in/3`, and friends.
1065+
1066+
Beware that returning the same key multiple times in `Kernel.put_in/3`, `Kernel.update_in/3`,
1067+
or `Kernel.get_and_update_in/3` will cause the previous values of the same key to be
1068+
overwritten as maps cannot have duplicate keys.
1069+
1070+
See `values/0` for a function that accesses all values in a map.
1071+
1072+
## Examples
1073+
1074+
iex> data = %{users: %{"john" => %{age: 27}, "meg" => %{age: 23}}}
1075+
iex> get_in(data, [:users, Access.keys()])
1076+
["john", "meg"]
1077+
iex> update_in(data, [:users, Access.keys()], fn name -> String.upcase(name) end)
1078+
%{users: %{"JOHN" => %{age: 27}, "MEG" => %{age: 23}}}
1079+
1080+
By returning `:pop` from an accessor function, you can remove the accessed key and value
1081+
from the map:
1082+
1083+
iex> require Integer
1084+
iex> numbers = %{1 => "one", 2 => "two", 3 => "three", 4 => "four"}
1085+
iex> get_and_update_in(numbers, [Access.keys()], fn num ->
1086+
...> if Integer.is_even(num), do: :pop, else: {num, to_string(num)}
1087+
...> end)
1088+
{[1, 2, 3, 4], %{"1" => "one", "3" => "three"}}
1089+
1090+
An error is raised if the accessed structure is not a map:
1091+
1092+
iex> get_in([1, 2, 3], [Access.keys()])
1093+
** (RuntimeError) Access.keys/0 expected a map, got: [1, 2, 3]
1094+
"""
1095+
@spec keys() :: Access.access_fun(data :: map(), current_value :: list())
1096+
def keys do
1097+
&keys/3
1098+
end
1099+
1100+
defp keys(:get, data = %{}, next) do
1101+
Enum.map(data, fn {key, _value} -> next.(key) end)
1102+
end
1103+
1104+
defp keys(:get_and_update, data = %{}, next) do
1105+
{reverse_gets, updated_data} =
1106+
Enum.reduce(data, {[], %{}}, fn {key, value}, {gets, data_acc} ->
1107+
case next.(key) do
1108+
{get, update} -> {[get | gets], Map.put(data_acc, update, value)}
1109+
:pop -> {[key | gets], data_acc}
1110+
end
1111+
end)
1112+
1113+
{Enum.reverse(reverse_gets), updated_data}
1114+
end
1115+
1116+
defp keys(_op, data, _next) do
1117+
raise "Access.keys/0 expected a map, got: #{inspect(data)}"
1118+
end
1119+
10021120
defp normalize_range(%Range{first: first, last: last, step: step}, list)
10031121
when first < 0 or last < 0 do
10041122
count = length(list)

0 commit comments

Comments
 (0)