@@ -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
425491end
0 commit comments