Skip to content

Commit 953aac7

Browse files
Abel Muiñojosevalim
authored andcommitted
Implement Access.at/1 (#4719)
It was described, but not implemented, on #4009
1 parent 07d5203 commit 953aac7

File tree

1 file changed

+66
-0
lines changed

1 file changed

+66
-0
lines changed

lib/elixir/lib/access.ex

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,4 +422,70 @@ defmodule Access do
422422
defp all([], _next, gets, updates) do
423423
{:lists.reverse(gets), :lists.reverse(updates)}
424424
end
425+
426+
@doc ~S"""
427+
Accesses the element at `index` (zero based) of a list.
428+
429+
## Examples
430+
431+
iex> list = [%{name: "john"}, %{name: "mary"}]
432+
iex> get_in(list, [Access.at(1), :name])
433+
"mary"
434+
iex> get_and_update_in(list, [Access.at(0), :name], fn
435+
...> prev -> {prev, String.upcase(prev)}
436+
...> end)
437+
{"john", [%{name: "JOHN"}, %{name: "mary"}]}
438+
iex> pop_in(list, [Access.at(0), :name])
439+
{"john", [%{}, %{name: "mary"}]}
440+
441+
When the index is out of bounds, `nil` is returned and the update function is never called:
442+
443+
iex> list = [%{name: "john"}, %{name: "mary"}]
444+
iex> get_in(list, [Access.at(10), :name])
445+
nil
446+
iex> get_and_update_in(list, [Access.at(10), :name], fn
447+
...> prev -> {prev, String.upcase(prev)}
448+
...> end)
449+
{nil, [%{name: "john"}, %{name: "mary"}]}
450+
451+
An error is raised for negative indexes:
452+
453+
iex> get_in([], [Access.at(-1)])
454+
** (FunctionClauseError) no function clause matching in Access.at/1
455+
456+
An error is raised if the accessed structure is not a list:
457+
458+
iex> get_in(%{}, [Access.at(1)])
459+
** (RuntimeError) Access.at/1 expected a list, got: %{}
460+
"""
461+
def at(index) when index >= 0 do
462+
fn(op, data, next) -> at(op, data, index, next) end
463+
end
464+
465+
defp at(:get, data, index, next) when is_list(data) do
466+
data |> Enum.at(index) |> next.()
467+
end
468+
469+
defp at(:get_and_update, data, index, next) when is_list(data) do
470+
get_and_update_at(data, index, next, [])
471+
end
472+
473+
defp at(_op, data, _index, _next) do
474+
raise "Access.at/1 expected a list, got: #{inspect data}"
475+
end
476+
477+
defp get_and_update_at([head | rest], 0, next, updates) do
478+
case next.(head) do
479+
{get, update} -> {get, :lists.reverse([update | updates], rest)}
480+
:pop -> {head, :lists.reverse([head | updates], rest)}
481+
end
482+
end
483+
484+
defp get_and_update_at([head | rest], index, next, updates) do
485+
get_and_update_at(rest, index - 1, next, [head | updates])
486+
end
487+
488+
defp get_and_update_at([], _index, _next, updates) do
489+
{nil, :lists.reverse(updates)}
490+
end
425491
end

0 commit comments

Comments
 (0)