我可以像 Elixir 中其他语言的闭包一样捕获块外的变量吗?

Can I capture variable outside of block like closure of other language in Elixir?

defmodule Test do
  use Retry
  import Stream

  def call_service(count) do
    IO.puts "call_service #{count}"
    %{"errors" => true}
  end

  def hello do
    count = 0
    retry_while with: linear_backoff(500, 1) |> take(5) do
      count = count + 1 # incorrect !!!
      call_service(count)
      |> case do
        result = %{"errors" => true} -> {:cont, result}
        result -> {:halt, result}
      end
    end
  end
end

以上示例引用自重试模块的document

我想在这里做的是知道 call_service 函数中的计数值。您可能已经注意到,该示例没有按我的预期运行。

我可以让计数值表示函数在 call_service 函数中被调用了多少次吗?

不,你不能在“[its] 块之外”赋值一个变量——这就是你听到“在 Elixir 中,一切都是赋值”的部分含义。

的常见模式
# pseudo-code
x = 0
for y in z {
  # do something
  x = x + 1
}

在 Elixir 中不起作用。相反,您经常需要依赖 map 和 reduce 函数(通常由 Enum 模块提供)。

考虑这个依赖 Enum 重复调用函数的简单模块。这里我们使用保护子句来限制我们实际进行的调用次数,即使我们在更大范围内枚举。

defmodule Foo do
  def repeat do
    Enum.each(1..10, fn n ->
      call_service(n)
    end)
  end

  defp call_service(count) when count > 7 do
    IO.puts("Whoops... called too many times!")
    {:error, "Called too many times"}
  end

  defp call_service(count) do
    IO.puts("Count is #{count}")
    {:ok, "Called the service!"}
  end
end



如果我们通过执行 Foo.repeat() 来 运行 这段代码,我们会看到如下输出:

Count is 1
Count is 2
Count is 3
Count is 4
Count is 5
Count is 6
Count is 7
Whoops... called too many times!
Whoops... called too many times!
Whoops... called too many times!

这表明我们枚举了整个数字范围 (1-10),但当计数大于 7 时我们执行了一些不同的操作。

更常见的是,当您想在“循环”过程中重新分配一个数字时,您最终会使用 Enum.map/2Enum.reduce/3

查看重试后,我认为 retry_while 无法达到您想要的结果。但是,您可以将 DelayStreams 与一个简单的 Enum.reduce_while/2 一起使用,它可以跟踪计数,如下例所示。

defmodule World do
  def call_service(count, limit) do
    IO.inspect(count, label: "count")
    cond do
      limit == 7 -> %{errors: true}
      count == limit -> %{errors: false}
      true -> %{errors: true}
    end
  end

  def hello(limit) do
    50
    |> Retry.DelayStreams.linear_backoff(1)
    |> Enum.take(limit * 2)
    |> Enum.reduce_while(0, fn delay, count ->
      Process.sleep(delay)
      case call_service(count, limit) do
        %{errors: false} = success -> {:halt, {:ok, success}}
        error when count == limit -> {:halt, {:error, error}}
        _ -> {:cont, count + 1}
      end
    end)
  end
end

结果如下:

iex(9)> World.hello 3
count: 0
count: 1
count: 2
count: 3
{:ok, %{errors: false}}
iex(10)> World.hello 7
count: 0
count: 1
count: 2
count: 3
count: 4
count: 5
count: 6
count: 7
{:error, %{errors: true}}
iex(11)>