Основная проблема заключается в том, что как только повторные попытки исчерпаны, следует повторить попытку, если и только если это было в пределах владельца вызова, а не вызванной ошибки по конвейеру.
Тем не менее, необходимо явно аннулировать __STACKTRACE__
и повторите попытку, если переданный func
был на самом деле вызвавшей функцию. Приведенный ниже код демонстрирует возможный подход.
Кроме того, важно заключить в кавычки последующий вызов retry/3
, мне любопытно, как версия, которую вы разместили, когда-либо компилировалась после того, как вы вернули содержимое retry
, указанное дважды.
defmodule RetryPipe do
defmacro left >>> right,
do: retry(left, right, 2)
def retry(param, func, 1),
do: quote do: unquote(param) |> unquote(func)
def retry(param, func, loops) do
{m, f} =
case func do
{{:., _, [{:__aliases__, _, [mod]}, fun]}, _, _} ->
{Module.concat([mod]), fun}
_ ->
{nil, nil}
end
quote do
try do
unquote(param) |> unquote(func)
rescue
ex ->
case __STACKTRACE__ do
[{unquote(m), unquote(f), _arity, _meta} | _] ->
unquote(retry(param, func, loops - 1))
_ -> reraise ex, __STACKTRACE__
end
end
end
end
end
Тест будет выглядеть следующим образом.
defmodule Test do
def f1(details),
do: IO.inspect(details, label: "OK[1]")
def f2(details) do
IO.inspect(details, label: "BAD[2]")
raise "failure"
end
def f3(details),
do: IO.inspect(details, label: "OK[3]")
end
import RetryPipe
%{a: 1, b: 2} >>> Test.f1() >>> Test.f2() >>> Test.f3()
#⇒ OK[1]: %{a: 1, b: 2}
#⇒ BAD[2]: %{a: 1, b: 2}
#
# ** (RuntimeError) failure
# iex:35: Test.f2/1
Обратите внимание, что этот код может потребовать дополнительной настройки при работе с локальными функциями, и, как правило, он чрезвычайно высок agile. В конце концов, __STACKTRACE__
не был изобретен для этого.
Если вы хотите повторить попытку несколько раз, просто сделайте это явно. Трубы могут выглядеть сексуально, но обычно ваши коллеги-разработчики не хотят видеть их в незнакомом коде.