tcoopman
Help me refactor chained map updates
Let’s say I have some code like this
defmodule Foo do
def foo do
state = %{state | a: a(state)}
state = %{state | b: b(state)}
state = %{state | c: c(state)}
state
end
def a(s)…
def b(s)…
def c(s)…
end
is there a cleaner way to do that?
Most Liked
Zurga
You could reduce
[a: &a/1, b: &b/1, c: &c/1]
|> Enum.reduce(state, fn {key, fun}, state -> Map.update!(state, key, fun) end)
mudasobwa
I see kinda XY Problem here to begin with. Functions retrieving the partial state from the full state could be either trivial wrappers for destructuring (def a(%{a: value}), do: value) or in-place calculations of some additional values based on the whole state. From your comments, I understood we are dealing with the latter.
That said, you potentially have inconsistencies in the state (when a bare a(state) gets called without updating the state.) The arguably best property of Erlang/Elixir (of the actor model in general) is its proven responsibility to keep the state consistent no matter what. That basically suggests to calculate changes on update, store them within the state, and retrieve them with a bare dot notation.
I understand that the refactor involving changes in many places is something you want to avoid, but I’d better refactor the state to keep the value you are to retrieve later and amend it in the single place in the way which does not depend on the call order (your example does,) and does not use readers.
adamu
Let me check the requirements: You want to update a map repetedly, specifying the key, and a function that has the same name as the key, where each function accepts the full state and returns the new value for the corresponding key. Each update should use the latest version of the state.
You could wrap this behaviour in a function:
def my_update(state, key, fun), do: %{state | key => fun.(state)}
Then call it like this:
def foo do
state
|> my_update(:a, &a/1)
|> my_update(:b, &b/1)
|> my_update(:c, &c/1)
end
You could even go further an make a macro that ensures the key and function name are the same, but that’s probably making things more confusing rather than simpler.
windexoriginal
Take a look at Map.update!/3.
def foo do
state
|> Map.update!(:a, &a)
|> Map.update!(:b, &b)
...
end
garrison
If the functions themselves updated the state you could pipe them.
def foo(state) do
state
|> a()
|> b()
|> c()
end
def a(state) do
%{state | a: :baz}
end
I do this a lot in GenServers.







