elixir - 在 Elixir 中,为什么不使用 case 语句而不是多个函数重载?

标签 elixir

我正在学习 Elixir 并且有点困惑为什么我们必须使用同一函数的多个定义而不是使用 case 语句进行分支。这是来自 Elixir in Action 的示例,第一版第 81 页,用于计算文件中的行数:

defmodule LinesCounter do
  def count(path) do
    File.read(path)
    |> lines_num
  end

  defp lines_num({:ok, contents}) do
    contents
    |> String.split("\n")
    |> length
  end

  defp lines_num({:error, _}), do: "error"
 end 

所以我们有两个 defp lines_num 实例来处理 :ok 和 :error 的情况。但是下面的内容难道不是以更简洁、更简洁的方式做同样的事情,并且只使用一个函数而不是三个函数吗?
defmodule LinesCounterCase do
  def count(file) do
    case File.read(file) do
      {:ok, contents} -> contents |> String.split("\n") |> length
      {:error, _} -> "error"
    end
  end
end

两者的工作方式相同。

当我开始使用 Elixir 时,我不想学习不正确的习语,因此我正在寻找以这种方式使用 case 语句的缺点。

最佳答案

书中的代码不是很惯用,它试图在不是最好的示例中显示多个函数子句和管道。

第 1 部分:关注点分离。

首先,一般约定说管道应该以“原始”变量开头,如下所示:

def count(path) do
  path
  |> File.read
  |> lines_num
end

第二件事是这段代码确实混合了职责。有时,处理函数返回的类型也有好处。如果我看到,那 lines_num返回整数或字符串,我真的会挠头。为什么lines_num读取文件时应该关心错误吗?答案是:不应该。它应该接受一个字符串并返回它的计算结果:
defp lines_num(contents) do #skipping the tuple here
  contents
  |> String.split("\n")
  |> length
end

现在您的计数功能中有两个选项。您可以在文件出现问题时让它崩溃或处理错误。在这个例子中,只返回了字符串“error”,所以最惯用的方法是完全跳过它:
def count(path) do
  path
  |> File.read! #note the "!" it means it will return just content instead {:ok, content} or rise an error
  |> lines_num
  end
end

Elixir 几乎总是提供 func!版本,正是出于这个原因 - 使管道更容易。

如果要处理错误,case 语句是最好的。 Unix 管道也不鼓励分支。
def count(path) do
  case File.read(path) do
    {:ok, contents} -> lines_num(contents)
    {:error, reason} -> do_something_on_error(reason)
  end
end

第 2 部分:多重功能子句在何处有意义?

多函数子句优于 case 语句的主要情况有两种:递归和多态。还有一些其他的,但这些对于初学者来说应该足够了。

多态性

假设您要制作 lines_num更通用地处理字符表示列表:
defp lines_num(contents) when is_binary(contents) do
  ...
end
defp lines_num(contents) when is_list(contents) do
  contents
  |> :binary.list_to_bin #not the most efficient way!
  |> lines_num
end

实现可能不同,但最终结果是相同的:不同类型的行数:"foo \n bar"'foo \n bar' .

递归
def factorial(0), do: 0
def factorial(n), do: n * factorial(n-1)

def map([], _func), do: []
def map([head, tail], func), do: [func.(head), map(tail)]

(警告:示例不是尾递归的)对此类函数使用 case 的可读性/惯用性要差得多。

结论:
  • 除非您知道自己在做什么,否则不要将函数头用于分支逻辑。
  • 如果您有分支逻辑,最好拆分管道。
  • 将函数子句用于多态性和递归。
  • 关于elixir - 在 Elixir 中,为什么不使用 case 语句而不是多个函数重载?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36119560/

    相关文章:

    elixir - 如何使用 Elixir OTP Supervisor/GenServer 进行快速工作

    elixir - 升级到 elixir 1.6.1 时出现 Mix.Shell.cmd 错误

    erlang - 如何启动特定的发布版本?

    elixir - 如何在 Repo.one 中使用 Repo.get 和 select like

    erlang - 在 Elixir 中比较 map 的方法

    erlang - 尝试在 Amazon Linux 2 上运行时获取 "Unusable Erlang runtime system!"

    elixir - 检查所有测试文件是否可编译

    xml - Elixir 和 Erlang 记录模式匹配

    elixir - 使用 get_and_update Elixir 修改并返回 map 列表

    elixir - 在 GenServer 中保存大 map ,这是一个有效的用例吗?