Reputation: 16710
Is there a way to interrupt a comprehension?
I'm thinking to something like Python's for
-break
-else
.
For instance, suppose I'm traversing a list of integers, and that while I'm generating a new list out of it, I want to check that it does not contain 0
.
Can I do something like this:
for x <- [1, 2, 0, 3] do
case x do
0 -> break
_ -> x
end
end
if broke do
:error
else
:ok
end
I guess I'm thinking too imperative, and that the correct functional approach would be something like this, with a recursion and an accumulator:
def traverse_and_check(list, acc \\ []) do
case list do
[] -> {:ok, reverse(acc)}
[0 | _tail] -> :error
[x | tail] -> traverse_and_check(tail, [x | acc])
end
end
Does Elixir have such a flow of control?
Upvotes: 3
Views: 5443
Reputation: 121000
If you are not interested in returning the collected part of the list, Elixir provides throw
/catch
exactly for that purpose:
In Elixir, a value can be thrown and later be caught.
throw
andcatch
are reserved for situations where it is not possible to retrieve a value unless by usingthrow
andcatch
.Those situations are quite uncommon in practice except when interfacing with libraries that do not provide a proper API.
I would consider this situation being the perfect example of “libraries [here—Elixir] that do not provide a proper API” :)
So here you go:
try do
for x <- [1, 2, 0, 3],
do: if x == 0, do: throw(:break), else: x
catch
:break -> :broken
end
#⇒ :broken
Please note, throw
/catch
are intended to control the flow, unlike raise
/rescue
.
Upvotes: 11
Reputation: 222118
You can use Enum.reduce_while/3
but the solution is worse than recursion in my opinion since it requires a case
afterwords to reduce the list for the non breaking case. The snippet below includes the reduce_while
implementation and an improved recursive solution based on your code:
defmodule A do
def f(list) do
Enum.reduce_while(list, [], fn x, acc ->
if x == 0, do: {:halt, :break}, else: {:cont, [x | acc]}
end)
|> case do
:break -> :break
list -> Enum.reverse(list)
end
end
def g(list, acc \\ [])
def g([], acc), do: Enum.reverse(acc)
def g([0 | _], _), do: :break
def g([x | xs], acc), do: g(xs, [x | acc])
end
IO.inspect(A.f([1, 2, 3]))
IO.inspect(A.f([1, 2, 0, 3]))
IO.inspect(A.g([1, 2, 3]))
IO.inspect(A.g([1, 2, 0, 3]))
Output:
[1, 2, 3]
:break
[1, 2, 3]
:break
Upvotes: 2
Reputation: 557
Have a look at the comprehensions page of the getting-started manual.
Comprehensions have optional filters that you can use in place of the standard filter/reduce functions. Try something like this:
for x <- [1, 2, 0, 3], x != 0, into: [] do: x
The documentation has some better examples, but this should do for your use case of removing zeros from a list in a comprehension.
Upvotes: 0