我正在尝试实现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生成的文件中确保这一点。
总有办法解决吗?
答案 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