Some utilities that I often wish I had are `Access.filter/1` and 
`Access.find/1`. Often I find myself doing something like

```
list_of_results = get_in(some_list, [:path_a, :path_b])

filtered_results = Enum.filter(list_of_results, &some_predicate/1)

get_in(filtered_results, [Access.all(), :a, :b])
```

The PR w/ documentation and information can be found 
here: https://github.com/elixir-lang/elixir/pull/6634

The implementation is below.

```
  @doc ~S"""
    Returns a function that accesses all elements of a list that match the 
provided predicate.

    The returned function is typically passed as an accessor to 
`Kernel.get_in/2`,
    `Kernel.get_and_update_in/3`, and friends.

    ## Examples

        iex> list = [%{name: "john", salary: 10}, %{name: "mary", salary: 
20}, %{name: "francine", salary: 30}]
        iex> get_in(list, [Access.filter(&(&1.salary >= 20)), :name])
        ["mary", "francine"]
        iex> get_and_update_in(list, [Access.filter(&(&1.salary <= 20)), 
:name], fn
        ...>   prev -> {prev, String.upcase(prev)}
        ...> end)
        {["john", "mary"], [%{name: "JOHN", salary: 10}, %{name: "MARY", 
salary: 20}, %{name: "francine", salary: 30}]}

    `filter/1` can also be used to pop elements out of a list or
    a key inside of a list:

        iex> list = [%{name: "john", salary: 10}, %{name: "mary", salary: 
20}, %{name: "francine", salary: 30}]
        iex> pop_in(list, [Access.filter(&(&1.salary >= 20))])
        {[%{name: "mary", salary: 20}, %{name: "francine", salary: 30}], 
[%{name: "john", salary: 10}]}
        iex> pop_in(list, [Access.filter(&(&1.salary >= 20)), :name])
        {["mary", "francine"], [%{name: "john", salary: 10}, %{salary: 20}, 
%{salary: 30}]}

    When no match is found, an empty list is returned and the update 
function is never called

        iex> list = [%{name: "john", salary: 10}, %{name: "mary", salary: 
20}, %{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: "mary", salary: 20}, 
%{name: "francine", salary: 30}]}

    An error is raised if the predicate is not a function or is of the 
incorrect arity:

        iex> get_in([], [Access.filter(fn a, b -> a == b end)])
        ** (FunctionClauseError) no function clause matching in 
Access.filter/1

    An error is raised if the accessed structure is not a list:

        iex> get_in(%{}, [Access.filter(fn a -> a == 10 end)])
        ** (RuntimeError) Access.filter/1 expected a list, got: %{}
  """
  def filter(func) when is_function(func, 1) do
    fn(op, data, next) -> filter(op, data, func, next) end
  end

  defp filter(:get, data, func, next) when is_list(data) do
    data |> Enum.filter(func) |> Enum.map(next)
  end

  defp filter(:get_and_update, data, func, next) when is_list(data) do
    get_and_update_where(data, func, next, [], [])
  end

  defp filter(_op, data, _func, _next) do
    raise "Access.filter/1 expected a list, got: #{inspect data}"
  end

  @doc ~S"""
    Returns a function that accesses all elements of a list that match the 
provided predicate.

    The returned function is typically passed as an accessor to 
`Kernel.get_in/2`,
    `Kernel.get_and_update_in/3`, and friends.

    ## Examples

        iex> list = [%{name: "john", salary: 10}, %{name: "mary", salary: 
20}, %{name: "francine", salary: 30}]
        iex> get_in(list, [Access.find(&(&1.salary >= 20)), :name])
        "mary"
        iex> get_and_update_in(list, [Access.find(&(&1.salary <= 20)), 
:name], fn
        ...>   prev -> {prev, String.upcase(prev)}
        ...> end)
        {"john", [%{name: "JOHN", salary: 10}, %{name: "mary", salary: 20}, 
%{name: "francine", salary: 30}]}

    `find/1` can also be used to pop elements out of a list or
    a key inside of a list:

        iex> list = [%{name: "john", salary: 10}, %{name: "mary", salary: 
20}, %{name: "francine", salary: 30}]
        iex> pop_in(list, [Access.find(&(&1.salary >= 20))])
        {%{name: "mary", salary: 20}, [%{name: "john", salary: 10}, %{name: 
"francine", salary: 30}]}
        iex> pop_in(list, [Access.find(&(&1.salary >= 20)), :name])
        {"mary", [%{name: "john", salary: 10}, %{salary: 20}, %{name: 
"francine", salary: 30}]}

    When no match is found, an empty list is returned and the update 
function is never called

        iex> list = [%{name: "john", salary: 10}, %{name: "mary", salary: 
20}, %{name: "francine", salary: 30}]
        iex> get_in(list, [Access.find(&(&1.salary >= 50)), :name])
        nil
        iex> get_and_update_in(list, [Access.find(&(&1.salary >= 50)), 
:name], fn
        ...>   prev -> {prev, String.upcase(prev)}
        ...> end)
        {nil, [%{name: "john", salary: 10}, %{name: "mary", salary: 20}, 
%{name: "francine", salary: 30}]}

    An error is raised if the predicate is not a function or is of the 
incorrect arity:

        iex> get_in([], [Access.find(fn a, b -> a == b end)])
        ** (FunctionClauseError) no function clause matching in 
Access.find/1

    An error is raised if the accessed structure is not a list:

        iex> get_in(%{}, [Access.find(fn a -> a == 10 end)])
        ** (RuntimeError) Access.find/1 expected a list, got: %{}
  """
  def find(func) when is_function(func, 1) do
    fn(op, data, next) -> find(op, data, func, next) end
  end

  defp find(:get, data, func, next) when is_list(data) do
    data |> Enum.find(func) |> next.()
  end

  defp find(:get_and_update, data, func, next) when is_list(data) do
    get_and_update_found(data, func, next, [])
  end

  defp find(_op, data, _func, _next) do
    raise "Access.find/1 expected a list, got: #{inspect data}"
  end

  defp get_and_update_where([head | rest], func, next, updates, gets) do
    if func.(head) do
      case next.(head) do
        {get, update} ->
          get_and_update_where(rest, func, next, [update | updates], [get | 
gets])
        :pop ->
          get_and_update_where(rest, func, next, updates, [head | gets])
      end
    else
      get_and_update_where(rest, func, next, [head | updates], gets)
    end
  end

  defp get_and_update_where([], _func, _next, updates, gets) do
    {:list.reverse(gets), :lists.reverse(updates)}
  end

  defp get_and_update_found([head | rest], func, next, updates) do
    if func.(head) do
      case next.(head) do
        {get, update} -> {get, :lists.reverse([update | updates], rest)}
        :pop -> {head, :lists.reverse(updates, rest)}
      end
    else
      get_and_update_found(rest, func, next, [head | updates])
    end
  end

  defp get_and_update_found([], _index, _next, updates) do
    {nil, :lists.reverse(updates)}
  end
```

-- 
You received this message because you are subscribed to the Google Groups 
"elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/1f77f02e-65f4-440c-a405-417205be2825%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to