I would like a piece of elixir code to do something like the following:
defmodule InfoManager do
def initial_state(queried_info) do
queried_info
|> Stream.map(fn i -> {i, atom_to_module(i).initial_value()} end)
|> Enum.into(%{})
end
def query(state, info) do
info
|> Stream.map(fn i -> {i, Map.get(state, i)} end)
|> Enum.into(%{})
end
def receive(state, news) do
state
|> Stream.map(&update_value_in_state(&1, news))
|> Enum.into(%{})
end
defp update_value_in_state({key, value}, news),
do: {key, atom_to_module(key).updated_value(value, news)}
defp atom_to_module(atom) do
atom
|> Atom.to_string()
|> (fn s -> "Elixir.Info." <> String.capitalize(s) end).()
|> String.to_atom()
end
end
so that I can define modules like this
defmodule Info.Day_number do
def initial_value, do: 0
def updated_value(value, _news), do: value + 1
end
defmodule Info.News do
def initial_value, do: nil
def updated_value(_value, news), do: news
end
so that I can use InfoManager
like this
state = InfoManager.initial_state([:day_number, :news])
state = InfoManager.receive(state, "irrelevant_news")
info = InfoManager.query([:day_number])
assert info == %{day_number: 1}
I am not satisfied by my code, right now. In particular, I am not convinced by the hack inside atom_to_module
. For one, it feels a waste of resources (think about calling thousand of times the function InfoManager.query/1
), but also it does not look like an idiomatic way to pass some module to a function.
What are the best practices to obtain (safe) polymorphism in situations like that? Should I use something like behaviours? Should I keep some more state around (like converting all atoms in the first call to the correct module and keep the mapping between the two around)?