【问题标题】:Pattern matching map with optional keys带有可选键的模式匹配映射
【发布时间】:2014-07-16 08:22:19
【问题描述】:

我有一个函数可以接收带有许多键的地图,其中一些是可选的。如何编写理解映射的函数签名,同时允许可选键默认为某些东西?

def handle_my_map(%{text: text, 
                    print_times: print_times, # this I want to default to 2
                    color: color # this I want to default to "Blue"
  }) do
  Enum.each(1..print_times, fn (_) -> IO.puts ["(", color, "): ", text] end)
end

Test.handle_my_map(%{text: "here", print_times: 5, color: "Red"})
# (Red): here
# (Red): here
# (Red): here
# (Red): here
# (Red): here

handle_my_map(%{text: "there"})
# => MatchError!

我希望它是:

handle_my_map(%{text: "where", print_times: 3})
# (Blue): where
# (Blue): where
# (Blue): where
handle_my_map(%{text: "there"})
# (Blue): there
# (Blue): there

类似于 ruby​​ 的关键字参数:

def handle_my_map(text: nil, print_times: 2, color: 'Blue')

【问题讨论】:

  • 这个handle_my_map/1 接受一个参数?
  • @Зелёный - 是的,一个地图参数

标签: elixir


【解决方案1】:

你可以使用Map.merge/2:

defmodule Handler do
  @defaults %{print_times: 2, color: "Blue"}

  def handle_my_map(map) do
    %{text: text, print_times: times, color: color} = merge_defaults(map)
    Enum.each(1..times, fn (_) -> IO.puts ["(", color, "): ", text] end)
  end

  defp merge_defaults(map) do
    Map.merge(@defaults, map)
  end
end

如果要允许 nils,可以使用 Map.merge/3 并将 merge_defaults/1 更改为:

defp merge_defaults(map) do
  Map.merge(@defaults, map, fn _key, default, val -> val || default end)
end

【讨论】:

  • 谢谢,我会试试的。你也有深度合并的建议吗?
  • 如果所有值都是映射,您可以执行类似于此处所做的操作:github.com/elixir-lang/elixir/blob/master/lib/mix/lib/mix/… 如果不是,您只需检查两个值是否都是映射,然后调用 Map.merge/2。
  • 澄清一下,“允许 nils”意味着用默认值覆盖 nils。顺便说一句,这个答案对Map.merge 来说是一个很好的文档,因为该文档在 Elixir 安装中还不存在。
  • @UriAgassi:elins 中的setVals 方法充当执行深度合并的镜头。免责声明,我只是写了它,但在尝试查找现有实现时首先在这里搜索了谷歌。话虽如此,José 建议的非镜头实现可能更简单。
【解决方案2】:

我可能会选择这样的东西:

defmodule Handler do
  @defaults %{print_times: 2, color: "Blue"}

  def handle_my_map(map) do
    %{text: text, print_times: times, color: color} = Dict.put_new(map, @defaults)
    Enum.each(1..times, fn (_) -> IO.puts ["(", color, "): ", text] end)
  end
end

如果您需要使用现有键处理 nil 值,您可以这样做:

defmodule Handler do
  @defaults %{print_times: 2, color: "Blue"}

  def handle_my_map(map) do
    %{text: text, print_times: times, color: color} = @defaults
    |> Dict.merge(map)
    |> Enum.into %{}, fn
      {key, nil} -> {key, @defaults[key]}
      {key, val} -> {key, val}
    end

    Enum.each(1..times, fn (_) -> IO.puts ["(", color, "): ", text] end)
  end
end

【讨论】:

  • 已编辑以解决您修改后的问题:)
  • 谢谢,我试试。虽然它看起来有点冗长,但也许如果不止一个位置需要它,它是一个很好的宏候选......
  • 无需为此类任务生成代码。我会将其提取到merge_with_defaults(my_map, the_defaults) func 以实现可重用性。
  • 你应该使用 Dict.merge/3 而不是 Enum.into/3。我打算将其添加为评论,但它太大了,所以我添加了另一个答案。
【解决方案3】:

嗯。我认为您需要编写另一个带有参数%{a: a, b: b}handle_my_map 函数。像这样:

  def handle_my_map(%{a: a, b: b, optional_c: c}) do
    a + b + c
  end

  def handle_my_map(%{a: a, b: b}) do
    a + b
  end

  YourModule.handle_my_map %{a: 1, b: 2}
  #=> 3
  YourModule.handle_my_map %{a: 1, b: 2, optional_c: 3}
  #=> 6

Elixir 将搜索与您的参数匹配的函数 handle_my_map,直到具有 arity 1 的函数结束

【讨论】:

  • 是的,但是如果有5个可选参数呢?我需要 32 个 (2^5) 实现...
  • 所以你不能使用模式匹配。你可以这样做:def handle_my_map(map), do: map[:a]+map[:b]+zero_if_nil (map[:optional_c]),其中zero_if_nil/1 是另一个辅助函数。
  • 很遗憾def method(%{a: a, b: b, c: c \\ default_c}) 不起作用。 :(
猜你喜欢
  • 2016-04-20
  • 1970-01-01
  • 2018-11-14
  • 2023-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-22
  • 1970-01-01
相关资源
最近更新 更多