Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

Right way to produce elixir struct in macro

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

Answers (1)

José Valim
José Valim

Reputation: 51429

You have a lot of indirection in your code that you really don't need. Examples:

  1. :"#{k}" could be simply k as k is already an atom
  2. You don't need Dict.keys/1 because you can pattern match on the key inside the loop
  3. You don't need a map because you can return a list directly from Enum.map (or from a comprehension)
  4. You don't need a macro because you can pass any expression to defstruct

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

Related Questions