Elixir:如何处理函数和nil值中的可选/默认参数?

时间:2016-03-31 09:55:27

标签: elixir

我对默认参数和零值有一般性问题。假设我有两个功能。一个调用另一个(这是一个辅助函数)。两者都有一个可选参数。

辅助函数只是将列表连接到带有连接符的字符串。将joiner传递给opts关键字列表中的第一个函数。 joiner的传递是可选的,默认为"AND"

defmodule ParamTest do
  def func_1(list, opts \\ []) do
    helper(list, opts[:joiner])
    # Do something else with the result
  end

  defp helper(list, joiner \\ "AND") do
    Enum.join(list, " #{joiner} ")
  end
end

# Example 1
["el 1", "el 2"] 
|> ParamTest.func_1(joiner: "AND")
# Result "el 1 AND el 2"

# Example 2
["el 1", "el 2"] 
|> ParamTest.func_1
# Result: "el 1  el 2"
# But it should be also "el 1 AND el 2"

问题是:在第二个例子中,opts [:joiner]将为nil。但它仍然存在,因此不会使用默认值。

一种可能的解决方案是使用case

defmodule ParamTest do
  def func_1(list, opts \\ []) do
    case is_nil(opts[:joiner]) do
      true -> helper(list)
      false -> helper(list, opts[:joiner])
    end
    # Do something else with the result
  end

  defp helper(list, joiner \\ "AND") do
    Enum.join(list, " #{joiner} ")
  end
end

另一种方法是为helper使用两个函数定义并使用模式匹配:

defmodule ParamTest do
  def func_1(list, opts \\ []) do
    case is_nil(opts[:joiner]) do
      true -> helper(list)
      false -> helper(list, opts[:joiner])
    end
  end

  defp helper(list, nil) do
    Enum.join(list, " AND ")
  end

  defp helper(list, joiner \\ "AND") do
    Enum.join(list, " #{joiner} ")
  end
end

但我觉得这不是很优雅,在更复杂的情况下会变得混乱。

对于这种情况,什么是更好的解决方案?

2 个答案:

答案 0 :(得分:7)

最好的解决方案是在帮助程序中强制使用joiner,并在func_1中提供默认选项。

def func_1(list, opts \\ [joiner: "AND"]) do
  helper(list, opts[:joiner])
  ...
end
defp helper(list, joiner) do
  ...
end

始终尝试将您的疑虑分开。 helper不属于您的公共API,因此您始终可以传递所有选项。让它只是做它的工作而不用担心默认值。

func_1是您的公共API,它应该担心默认值。你想指定" AND"默认情况下是joiner,所以这样做而不是默认为空选项列表。当有人正在阅读您的代码时,他不需要更深入地检查" AND"来自并且很容易弄明白,他可以通过这个选项而无需阅读文档甚至函数体。

通常一个好主意只是为了方便顶级函数(API)而使默认值只是明确地传递所有内容。否则,您必须在每个级别检查是否通过选项,就像您在case示例中所做的那样。这很容易出错。

答案 1 :(得分:4)

没有比我认为的更好的解决方案。就个人而言,我会问自己以下问题:

  • 我确定我需要helper/2 私人功能的默认参数吗?我对此没有信心,但我觉得私有函数的默认\\ args可能是某种代码味道。
  • 如果我使用\\默认参数,我想在哪里处理复杂性? :)

如果我必须选择,在这种特殊情况下,我可能会根据helper/1选项的存在单独调用helper/2:joiner

defmodule ParamTest do
  def func_1(list, opts \\ []) do
    if joiner = opts[:joiner] do
      helper(list, joiner)
    else
      helper(list)
    end
  end

  defp helper(list, joiner \\ "AND") do
    Enum.join(list, " #{joiner} ")
  end
end

然而,正如我上面所说,由于helper/2是一个私人功能,它可能是有意义的(取决于你的用例,这个太小,我们无法做出更周到的决定:P)移动可选的连接器完全到“系统”的边界,即通过使用选项的默认值只到func_1/2

defmodule ParamTest do
  def func_1(list, opts \\ []) do
    helper(list, opts[:joiner] || "AND")
  end

  defp helper(list, joiner) do
    Enum.join(list, " #{joiner} ")
  end
end

同样,在您的使用案例中,这可能无法很好地扩展,但我觉得这对我们从问题中获得的信息是最好的:)。