【问题标题】:Elixir: How to deal with optional / default parameters in functions and nil values?Elixir:如何处理函数和 nil 值中的可选/默认参数?
【发布时间】:2019-09-12 15:41:52
【问题描述】:

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

helper 函数只是将一个列表连接到一个带有连接器的字符串。 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] 将为零。但它仍然存在,因此不会使用默认值。

一种可能的解决方案是使用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

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

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

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

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

【问题讨论】:

    标签: elixir


    【解决方案1】:

    最好的解决方案是在 helper 中强制 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”连接符,所以不要默认为空选项列表。当开发人员阅读您的代码时,他们无需更深入地检查“AND”的来源,并且可以轻松找出,他们可以通过此选项而无需阅读文档甚至函数体。

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

    【讨论】:

    • 我什至比@whatyouhide 的版本更喜欢您的版本,因为它使第一次阅读我的代码的人非常清楚默认设置。谢谢
    • 我必须撤回我之前的声明。一旦使用 opts 参数传入更多选项,所有内容都会被覆盖,并且此解决方案不再起作用。 ["el 1", "el 2"] |> ParamTest.func_1(something: "whatever") 将产生"el 1 el 2"
    • 你是对的。如果您有更多选项并且只想覆盖其中一些选项,opts[:joiner] || "AND" 会更好。或者,在函数的第一行中,您可以使用opts = opts ++ [joiner: "AND"],但这只有在所有选项都有默认值并且有几个选项时才有意义。
    【解决方案2】:

    在我看来,没有比您已有的更好的解决方案了。就个人而言,我会问自己以下问题:

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

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

    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
    

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

    【讨论】:

    • 我同意私有函数的默认参数。我喜欢你的第二个解决方案。谢谢。
    • 如果只从第一个函数调用助手,那么使用双重默认值很尴尬。第二种解决方案很尴尬,因为你有 ||在函数调用内部。你最好把你的默认设置为 opts \\ [joiner: "AND"]
    • @CaptChrisD 正如其他人在 cmets 中向其他函数指出的那样,[joiner: "AND"] 作为默认值 通常 不是一个好主意,因为该函数可以接受许多选项(一个函数很少接受一个选项)。
    猜你喜欢
    • 2019-12-30
    • 2013-10-08
    • 1970-01-01
    • 1970-01-01
    • 2010-09-23
    • 2021-12-25
    • 2011-09-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多