打破一个理解

Break a comprehension

有没有办法打断理解?

我正在考虑 Python 的 for-break-else。 例如,假设我正在遍历一个整数列表,当我从中生成一个新列表时,我想检查它是否不包含 0.

我可以这样做吗:

for x <- [1, 2, 0, 3] do
    case x do
        0 -> break
        _ -> x
    end
end
if broke do
    :error
else
    :ok
end

我想我想的太命令了,正确的函数式方法应该是这样的,带有递归和累加器:

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

Elixir 有这样的控制流程吗?

查看 getting-started 手册的 comprehensions 页。

Comprehensions 有可选的过滤器,您可以使用它们代替标准 filter/reduce 函数。尝试这样的事情:

for x <- [1, 2, 0, 3], x != 0, into: [] do: x

文档中有一些更好的示例,但这应该适用于您在理解中从列表中删除零的用例。

您可以使用 Enum.reduce_while/3,但我认为该解决方案比递归更糟糕,因为它需要 case 后缀来减少非中断情况的列表。下面的代码片段包括 reduce_while 实现和基于您的代码的改进递归解决方案:

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]))

输出:

[1, 2, 3]
:break
[1, 2, 3]
:break

如果您对返回列表的收集部分不感兴趣,Elixir 提供 throw/catch 正是为此目的:

In Elixir, a value can be thrown and later be caught. throw and catch are reserved for situations where it is not possible to retrieve a value unless by using throw and catch.

Those situations are quite uncommon in practice except when interfacing with libraries that do not provide a proper API.

我认为这种情况是 “不提供适当 API 的库 [here—Elixir]” 的完美示例 :)

所以给你:

try do
  for x <- [1, 2, 0, 3],
    do: if x == 0, do: throw(:break), else: x
catch
  :break -> :broken
end
#⇒ :broken

请注意,throw/catch 旨在控制流量,与 raise/rescue 不同。