在Elixir中的另一个文件中加载另一个模块之前,如何加载模块?

时间:2019-03-05 08:49:01

标签: elixir

我正在尝试实现Protobuf的某些功能,并且有一个案例:一个模块需要在编译时调用另一个模块。它们在同一个文件中,很难确保它们的顺序。

defmodule FakeProtobuf do
  defmacro __using__(opts) do
    quote do
      # register_attribute fields and set fields
      @before_compile FakeProtobuf
    end
  end

  defmacro __before_compile__(_) do
    # result = Enum.map(fields, fn ...)

    # even Code.ensure_loaded doesn't work
    result = %{bar: Bar.default}
    quote do
      def default do
        unquote(Macro.escape(result))
      end
    end
  end
end

defmodule Foo do
  use FakeProtobuf
  # field :bar, type: Bar
end

defmodule Bar do
  def default do
    "bar"
  end
end

在这段代码中,与字段相关的一些宏细节被忽略了,但是主要思想就像我上面所说的那样。无法编译此代码,因为即使调用Code.ensure_loaded(Bar)时Foo编译时Bar也不可用。我需要这样做是因为我想在编译时而不是运行时运行一些代码以节省一些时间。

如果Bar在Foo之前​​或在另一个文件中定义,则可以使用。但是很难在protobuf生成的文件中确保这一点。

总有办法解决吗?

1 个答案:

答案 0 :(得分:0)

只需在其他模块之前定义Bar,即可编译您的代码。

当编译器开始编译模块FakeProtoBuf时,它不了解该模块内部的定义。

它将开始编译文件,并遇到您的__before__compile/1宏。扩展该宏时,需要在模块B中调用函数,编译器尚未看到该函数,因此会出错。

** (UndefinedFunctionError) function Bar.default/0 is undefined (module Bar is not available)

要解决此问题,我们将Bar模块移到FakeProtoBuf模块的定义之前。这样,编译器可以安全地调用该函数,因为Bar将被完全编译。

最后,编译器将编译Foo,将在__using__/1中执行FakeProtoBuf,并注入@before_compile,从而调用宏__before__compile__FakeProtoBuf模块中。

  def default do
    "bar"
  end
end

defmodule FakeProtobuf do
  defmacro __using__(_opts) do
    quote do
      # register_attribute fields and set fields
      @before_compile FakeProtobuf
    end
  end

  defmacro __before_compile__(_) do
    result = %{bar: Bar.default()}

    quote do
      def default do
        unquote(Macro.escape(result))
      end
    end
  end
end

defmodule Foo do
  use FakeProtobuf
  # field :bar, type: Bar
end