метапрограммирование эликсира для атрибутов модуля - PullRequest
0 голосов
/ 20 мая 2018

У меня есть этот модуль

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

Я знаю, что могу определить функцию как:

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

И может быть вызван так:

Lang.lang("es")

Также даже измените его имя функции, например:

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

И вызывайтесь так:

Lang.lang_es

Но возможно ли сделать то же самое с атрибутом модуля?

А будучи атрибутом модуля скомпилированным (?) Я думаю, что невозможно инициализировать его из макроса?может быть, я должен был бы сделать это в макросе before_compile?

Я хотел бы получить для целей примера доступ к Lang.lang_es в качестве атрибутов @lang_es и @lang_en LangLoader

1 Ответ

0 голосов
/ 21 мая 2018

Да, этого можно добиться с помощью Module.put_attribute/3 (я создал MCVE из вашего исходного кода):

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"}

Приведенный выше код объявляет накопленный атрибут (@attr :foo, за которым следует @attr :bar, будет производить [:foo, :bar] значение вместо перезаписи значения атрибута, одного атрибута и функции.

Обратите внимание, что нет доступа к атрибуту модуля извне, поскольку атрибуты модуляявляются объектами времени компиляции.

...