Skip to content

Instantly share code, notes, and snippets.

@kblake
Last active November 30, 2016 08:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kblake/b04e85c8e625ba650e1690f6f240d6f4 to your computer and use it in GitHub Desktop.
Save kblake/b04e85c8e625ba650e1690f6f240d6f4 to your computer and use it in GitHub Desktop.
Thought experiment where I want to short circuit the processing of a list of operations if one of them fails.
defmodule ExpensiveOperationWorker do
# recursion and pattern matching
def process([head | tail]) do
operation_passed?(head) && process(tail)
end
def process([]), do: true
# Enum.reduce_while
def process_operations(operations) do
Enum.reduce_while(operations, true, fn number, acc ->
if operation_passed?(number) do
{:cont, acc}
else
{:halt, acc}
end
end)
end
# Enum.reduce
def another_way_to_process(operations) do
Enum.reduce(operations, true, &(&2 && operation_passed?(&1)))
end
# Contrived, expensive operation :)
defp operation_passed?(number) do
rem(number,2) != 0
end
end
# 7 and 9 do not get evaluated
# because processing short-circuits at 6
expensive_operations = [1,3,5,6,7,9]
ExpensiveOperationWorker.process(expensive_operations)
ExpensiveOperationWorker.process_operations(expensive_operations)
ExpensiveOperationWorker.another_way_to_process(expensive_operations)
#processes all since none are even
expensive_operations = [1,3,5,7,9,11]
ExpensiveOperationWorker.process(expensive_operations)
ExpensiveOperationWorker.process_operations(expensive_operations)
ExpensiveOperationWorker.another_way_to_process(expensive_operations)
@gausby
Copy link

gausby commented Nov 29, 2016

How about moving the private operation_passed?/1 into a guard clause and replacing the if-expression with a multihead function:

  # Use Enum.reduce_while to short circuit ###########
  def process_operations(operations) do
    Enum.reduce_while(operations, true, fn
      (number, acc) when rem(number, 2) != 0 ->
        {:cont, acc};

      (_, acc) ->
        {:halt, acc}
    end)
  end

The continuation style looks less magical to me.

@kblake
Copy link
Author

kblake commented Nov 29, 2016

@gausby I like the guard approach!

@orestis
Copy link

orestis commented Nov 30, 2016

How about:

def process(operations) do
  Enum.drop_while(operations, &operation_passed?/1)
end

It has the added benefit of giving you a [failed|rest] result.

If you want to collect the results you can use Enum.split_while/2 which will return {passed, [failed|rest]}.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment