如何更新地图,但仅在密钥已存在时更新

时间:2017-09-10 21:30:13

标签: elixir

我一直在编写很多长生不老药和一件不断修剪我的代码的事情就是当我想要仅在密钥已经存在的情况下更新地图的值时,我最终会得到这样的代码:

def incSomething(state, key) do
  {_, state} = get_and_update_in(state.way.down.there[key], fn
    nil -> :pop
    j -> {nil, j + 1}
  end)
state
end

有时会涉及很多代码,有时会嵌套get_and_update_ins,所以它会变得混乱。

我最初发现自己想要使用update_in/2宏,但它似乎更像是一个upsert而不是更新,而不像sql中updatereplace into之间的区别。

Map.update/4允许您设置默认值,但不允许您不设置任何内容。如果密钥丢失,则Map.update!/3完全错误。

用标准语言执行此操作是否有一种不那么尴尬的方法?或者我必须自己写吗?

2 个答案:

答案 0 :(得分:5)

您可以使用Map.replace/3做到这一点 - 只有在地图中存在该密钥时才更新密钥的值。

此功能仅在Elixir 1.5之后可用,对于以前的版本,您需要自己实现它。幸运的是,这很容易。

def update_existing(map, key, value) do
  case map do
    %{^key => _} -> %{map | key => value}
    %{} -> map
  end
end

或者,如果您想使用函数式更新,可以稍微修改它:

def update_existing(map, key, fun) do
  case map do
    %{^key => old} -> %{map | key => fun.(old)}
    %{} -> map
  end
end

答案 1 :(得分:0)

这是我最终实际使用的内容。因为我的数据类型是深度嵌套的,所以我更喜欢使用update_in/2

样式的宏
defmodule ReplaceIn do

  defmacro replace_in(path, fun) do
    quote do
      get_and_update_in(unquote(path), fn
        nil -> :pop
        x -> {nil, unquote(fun).(x)}
      end) |> elem(1)
    end
  end

  def replace_in(data, keys, fun) do
    get_and_update_in(data, keys, fn
      nil -> :pop
      x -> {nil, fun.(x)}
    end) |> elem(1)
  end
end

它可以与update_in/2完全相同。

> require ReplaceIn
> a = %{b: %{c: 1}}
> ReplaceIn.replace_in(a.b[:c], fn x -> x + 1 end)
%{b: %{c: 2}}
> ReplaceIn.replace_in(a.b[:d], fn x -> x + 1 end)
%{b: %{c: 1}}