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

Dav*_*aly 3 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
Run Code Online (Sandbox Code Playgroud)

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

我最初发现自己想要使用update_in/2宏,但似乎充当比更新而不是像之间的区别更多的UPSERT的updatereplace into在SQL.

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

在标准语言中这样做有一种不那么尴尬的方法吗?或者我必须自己写?

mic*_*ala 5

您可以使用Map.replace/3这样做 - 只有在地图中存在该键时才更新键的值.

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

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

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

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


Dav*_*aly 1

这是我最终实际使用的。因为我的数据类型是深层嵌套的,所以我更喜欢使用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
Run Code Online (Sandbox Code Playgroud)

并且它的使用方式与 完全相同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}}
Run Code Online (Sandbox Code Playgroud)