Elixir:如何避免深层嵌套的case语句?

时间:2018-06-13 00:25:17

标签: elixir

我们假设我有一个函数main_function取决于其他三个函数的结果,每个函数都可以返回{:ok, result}{:error, error}。我怎样才能避免在javascript中使用深度嵌套的case语句,感觉就像回调地狱。

示例:

def func1(input) do
  case SOMECONDITION do
    {:ok, result} ->
      {:ok, result}
    {:error, error} ->
      {:error, error}
  end
end

def func2(input) do
  case SOMECONDITION do
    {:ok, result} ->
      {:ok, result}
    {:error, error} ->
      {:error, error}
  end
end

def func3(input) do
  case SOMECONDITION do
    {:ok, result} ->
      {:ok, result}
    {:error, error} ->
      {:error, error}
  end
end

def main_function(input) do
  case func1(input) do
    {:ok, result} ->
      case func2(result) do
        {:ok, result} ->
          case func3(result) do
            {:ok, result} ->
              {:ok, EXPECTED_OUTCOME}
            {:error, error} ->
              {:error, error}
          end
        {:error, error} ->
          {:error, error}
      end
      {:error, error} ->
        {:error, error}  
    end
  end
end

这感觉不对......

2 个答案:

答案 0 :(得分:15)

[编辑]:我为一个涵盖这个概念的精彩演讲添加了一个便利链接,以及来自下面ElixirConf 2018的更复杂需求的解决方案。

别担心 - Elixir让你满意。您想要this special formwith/1

当且仅当它们与预期结果匹配时,

with/1才会继续执行函数。

你的主要功能基本上看起来像:

def main_function(input) do
  with {:ok, result_1} <- func1(input),
       {:ok, result_2} <- func2(result_1),
        ...,
  do: {:ok, EXPECTED_OUTCOME}
end

当它找不到匹配时,比如因为有一个像{:error, _error}这样的元组,特殊形式将返回遇到的第一个错误并停止执行这些函数。

您还可以添加其他条件。我使用过这个例子的例子是当用户可能期望某些操作需要完成许多功能时,我想提醒他们同样的事情,无论它在哪里失败:

def main_function(input) do
  with {:ok, result_1} <- func1(input),
       {:ok, result_2} <- func2(result_1),
        ... do
    {:ok, EXPECTED_OUTCOME}
  else
    _error ->
      {:error, "Couldn't complete action"}
  end
end

其他资源:

以下是来自Credo作者关于此概念的精彩演讲,由ElixirConf 2018提供:https://www.youtube.com/watch?v=ycpNi701aCs&t=473s

以下是with/1上的精彩帖子:https://www.erlang-solutions.com/blog/exploring-with-the-elixir-special-form.html

答案 1 :(得分:2)

回答对已接受答案的评论:

  

这是一种有趣的方法。但是,让我们说func1func2正在写入数据库。如果func1返回错误,我根本不想执行func2。会是这种情况吗?

如果您使用 Ecto ,还有其他选择。

让我们说func1func2func3 所有写入数据库,但步骤在func2处失败,表示func1已写入数据库。

让他们说他们返回{:ok, result}{:error, error}

Ecto.Multi救援!

alias Ecto.Multi
alias MyApp.Repo

def main_function(input) do
  result =
    Multi.new()
    |> Multi.run(:result1, fn _ -> func1(input) end)
    |> Multi.run(:result2, &func2(&1.result1))
    |> Multi.run(:result3, &func3(&1.result2))
    |> Repo.transaction()

  case result do
    {:ok, %{result1: _, result2: _, result3: _}} ->
      {:ok, EXPECTED_OUTCOME}

    {:error, error, _changes_so_far} ->
      {:error, error}
  end
end

使用Multi,返回错误元组将中止任何进一步的操作并使整个多次失败。此外,由于它使用了一个事务,任何成功的事都将被回滚。