我有一个关于默认参数和零值的一般问题.假设我有两个功能.一个调用另一个(这是一个辅助函数).两者都有一个可选参数.
辅助函数只是将列表连接到带有joiner的字符串.将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"
Run Code Online (Sandbox Code Playgroud)
问题是:在第二个例子中,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
Run Code Online (Sandbox Code Playgroud)
另一种方法是使用辅助函数的两个函数定义并使用模式匹配:
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
Run Code Online (Sandbox Code Playgroud)
但我觉得这不是很优雅,在更复杂的情况下会变得混乱.
对于这种情况,什么是更好的解决方案?
最好的解决方案是在帮助程序中强制使用joiner,并提供默认选项func_1.
def func_1(list, opts \\ [joiner: "AND"]) do
helper(list, opts[:joiner])
...
end
defp helper(list, joiner) do
...
end
Run Code Online (Sandbox Code Playgroud)
始终尝试分离您的疑虑.helper不属于您的公共API,因此您始终可以传递所有选项.让它只是做它的工作而不用担心默认值.
func_1是你的公共API,它应该担心默认值.您希望默认指定"AND"加入者,因此请执行此操作而不是默认为清空选项列表.当有人正在阅读你的代码时,他不需要更深入地检查"AND"来自哪里并且可以很容易地弄清楚,他可以通过这个选项而无需阅读文档甚至函数体.
通常,为了方便顶层函数(API)而使默认值通常是一个好主意,并且只是明确地传递所有内容.否则,您必须在每个级别检查,如果选项是否像您在示例中所做的那样传递case.这很容易出错.
在我看来,没有比你已经拥有的更好的解决方案了。就我个人而言,我会问自己以下问题:
helper/2 我确定我需要私有函数的默认参数吗?我对此没有信心,但我觉得\\私有函数的默认参数可能是某种代码味道。\\?:)如果我必须选择,在这种特殊情况下,我可能会根据选项的存在分别进行调用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
Run Code Online (Sandbox Code Playgroud)
然而,正如我上面所说,由于helper/2是一个私有函数,因此将可选连接器完全移动到“系统”,即只是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
Run Code Online (Sandbox Code Playgroud)
同样,这在您的用例中可能无法很好地扩展,但我觉得这是我们利用问题中获得的信息所能做的最好的事情:)。