benhoven
Use case for Enum.reduce_while without accumulator
Hi Everybody,
Recently I discovered interesting use case for reduce_while that does not use acc as accumulator → it doesn’t carry / use the variable in subsequent loops.
Could you please take a look and tell me if it’s clever or stupid?
Enum.reduce_while(some_enumerable, {:error, :show_this_when_empty_input}, fn some_item, _ ->
with {:ok, something} <- something(some_item),
{:ok, something} <- something_else(something) do
{:halt, {:ok, something}}
else
# known error - continue OR finish with this error
{:error, :expected_message} = error -> {:cont, error}
# unknown error - halt immediately
{:error, _} = error -> {:halt, error}
end
end)
What I can get from that is:
{:error, :show_this_when_empty_input}when enumerable is an empty list{:error, :expected_message}when no element in enumerable passes the test (the function) in 3rd argument{:ok, result}first positive result from the function → first element that passes and then stop processing{:error, anything}for results / errors that are not expected
Marked As Solved
Eiji
Sorry, I did not get it. Since we accept any function and expect not nil all mentioned clauses could be changed to return “something” like {:error, error} or {:ok, result} and optionally for {:error, :expected_message} we may want to return nil in order to skip specific item.
If you want you can also write a really simple few-lines function like:
defmodule Example do
def sample(list, default \\ nil, func)
def sample([], default, _func), do: func
def sample([head | tail], default \\ nil, func) do
case func.(head) do
{:ok, something} -> something
{:error, :expected_message} = error -> sample(tail, error, func)
{:error, _} = error -> error
end
end
end
and use it like:
Example.sample(some_enumerable, {:error, :show_this_when_empty_input}, fn some_item, _ ->
with {:ok, something} <- something(some_item),
{:ok, something} <- something_else(something) do
{:ok, something}
else
{:error, _} = error -> error
end
end)
Look that in case clauses you may simply add support for {:halt, acc} and {:cont, acc} if you do not want to change anonymous function from an original post. You may even combine Example.sample/3 with such anonymous function.
Also Liked
kokolegorille
I would also try to move the code to Enum.find, or Enum.find_value, but the condition is more complex, and not really a truthy kind of condition.
It’s hard to capture the with, with Enum.find ![]()
wolf4earth
I’ve written code similar to this, so yeah, I think that’s a perfectly reasonable usage of Enum.reduce_while. ![]()
Eiji
Your code looks good except one point. Calling Enum.reduce* functions does not makes sense if you do not need an accumulator. I would rather write something like:
func = fn
# return error if needed - equivalent of: {:halt, {:error, error}}
x when rem(x, 2) != 0 -> {:error, 2}
# skip item if needed - equivalent of: {:cont, acc}
x when rem(x, 3) != 0 -> nil
# return ok tuple for desired item - equivalent of: {:halt, {:ok, item}}
x -> {:ok, x}
end
iex> Enum.find_value(1..3, func)
{:error, 2} # error returned
iex> Enum.find_value([2, 4], func)
nil # all skipped
iex> Enum.find_value([2, 4, 6], func)
{:ok, 6} # ok returned
For more information please take a look at Enum.find_value/2 documentation.
Last Post!
benhoven
Thank you very much @Eiji, I took you example sample and I created a functional example / comparison of my old cold and your recommended new code with recursion.
Just one small correction - second sample has do: default and I didn’t use default value for default argument.
defmodule DoSomething do
def function_that_shoud_not_fail(value) do
# dummy code
# {:error, :this_is_not_expected_terminate_me_now}
{:ok, value <> "-abc"}
end
def another_function(value) do
# dummy code
# {:error, :this_is_normal}
{:ok, value <> "-xyz"}
end
#####################################################
# old way
def test_this_using_reduce_while(enumerable) do
Enum.reduce_while(enumerable, {:error, :enumerable_is_empty}, fn single_element, _ ->
with {:ok, value} <- function_that_shoud_not_fail(single_element),
{:ok, _} = result <- another_function(value) do
# sucess -> finish (halt)
{:halt, result}
else
# continue processing other elements / finish with this error
{:error, :this_is_normal} = result -> {:cont, result}
# unknown exception -> halt immediately
{:error, _} = result -> {:halt, result}
end
end)
end
#####################################################
# new way
defp sample([], default, _func),
do: default
defp sample([head | tail], _default, func) do
case func.(head) do
# sucess -> finish (halt)
{:ok, _} = result -> result
# continue processing other elements / finish with this error
{:error, :this_is_normal} = error -> sample(tail, error, func)
# unknown error -> finish (halt)
{:error, _} = error -> error
end
end
def test_this_using_recursion(enumerable) do
sample(enumerable, {:error, :enumerable_is_empty}, fn single_element ->
with {:ok, value} <- function_that_shoud_not_fail(single_element),
{:ok, _} = result <- another_function(value) do
result
else
{:error, _} = error -> error
end
end)
end
#####################################################
end
result = DoSomething.test_this_using_reduce_while(["aaa", "bbb", "ccc", "ddd", "eee", "fff"])
IO.puts("result: #{inspect(result)}")
result = DoSomething.test_this_using_recursion(["aaa", "bbb", "ccc", "ddd", "eee", "fff"])
IO.puts("result: #{inspect(result)}")
That would basically create Enum.reduce_while that doesn’t have acc. Very cool
.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex









