Reputation: 121010
I have the following design pattern: I have an elixir module, that responds to growing/changing amount of functions/0
, called Defaults
. I have also CustomConfig
module, that is basically the struct, deriving defaults and assumed to be instantiated like:
%CustomConfig{ foo: "bar" }
where initialized properties are overwritten, others are taken from Defaults
and those, not having a function with the same name in Defaults
are rejected. So far, so good.
To implement this behaviour independent on content (the list of functions in Defaults
,) I use a macro (in other module, since one can not use macro, defined in the same module, within struct declaration):
defmacro define_struct_with_defaults do
quote do
defstruct Map.to_list(
quote do: unquote(Enum.reduce(Dict.keys(
Defaults.__info__(:functions)), %{}, fn(k, acc) ->
Map.put(acc, :"#{k}", apply(Defaults, :"#{k}", []))
end)))
end
end
While this works fine, I am pretty sure, that there should be more straightforward/elegant/less-cumbersome way to achieve this functionality.
So my question would be: how one could declare defstruct
from the Map
without dancing this paso doble around map-reduce?
Upvotes: 1
Views: 1596
Reputation: 51429
You have a lot of indirection in your code that you really don't need. Examples:
:"#{k}"
could be simply k
as k
is already an atomDict.keys/1
because you can pattern match on the key inside the loopdefstruct
Here is how you can rewrite the code:
defmodule Default do
def foo, do: 1
def bar, do: 2
end
defmodule Config do
data =
# Get all functions with 0 arity and the respective default
for {k, 0} <- Default.__info__(:functions) do
{k, apply(Default, k, [])}
end
defstruct data
end
One of the benefits of Elixir is that you can write assertive code. If you leverage that, you will become more and more confident in your code.
Upvotes: 6