1

I have this module

defmodule ElixirMeta.LangLoader do

  @external_resource [Path.join([__DIR__, "es.json"]),
                      Path.join([__DIR__, "en.json"])]

  defmacro __using__(_) do
    for lang <- ["es", "en"] do
      {:ok, body} = File.read(Path.join([__DIR__, "#{lang}.json"]))
      {:ok, json} = Poison.decode(body)
      quote do
        def lang(unquote(lang)), do: unquote(Macro.escape(json))
      end
    end
  end
end

defmodule ElixirMeta.Lang do
  use ElixirMeta.LangLoader
end

I know I can define a function like:

def lang(unquote(lang)), do: unquote(Macro.escape(json))

And can be called like this:

Lang.lang("es")

Also even modify it's function name, like this:

def unquote(:"lang_#{lang}")(), do: unquote(Macro.escape(json))

And be called like this:

Lang.lang_es

But is it possible to do the same with a module attribute?

And being the module attribute compiled (?) I think is not possible to initialize it from the macro? maybe I would have to do it within before_compile macro?

I would like to access, for the purpose of the example, Lang.lang_es as a @lang_es and @lang_en LangLoader attributes

1 Answer 1

5

Yes, one can achieve that with Module.put_attribute/3 (I have created an MCVE out of your initial code):

defmodule ElixirMeta.LangLoader do
  defmacro __using__(_) do
    [
      (quote do: Module.register_attribute __MODULE__,
        :langs, accumulate: true) |
      for lang <- ["es", "en"] do
        quote do
          def lang(unquote(lang)), do: unquote(lang)
          Module.put_attribute __MODULE__,
            :"lang_#{unquote(lang)}", unquote(lang)
          Module.put_attribute __MODULE__,
            :langs, unquote(lang)
        end
      end
    ]
  end
end

defmodule ElixirMeta.Lang do
  use ElixirMeta.LangLoader

  def test do
    IO.inspect {
      @lang_es,
      Enum.find(@langs, & &1 == "es"),
      lang("es")
    }, label: "Variants"
  end
end

ElixirMeta.Lang.test
#⇒ Variants: {"es", "es", "es"}

The code above declares the accumulated attribute (@attr :foo followed by @attr :bar would produce [:foo, :bar] value instead of overwriting the attribute value, single attribute and the function.

Please note, that there is no way to access module attribute from outside, since module attributes are compile-time entities.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.