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?
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.
throwandcatchare reserved for situations where it is not possible to retrieve a value unless by usingthrowandcatch.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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With