Reputation: 1020
I'm writing one macro to use in some modules that adds documentation to each function. Is this possible? Someone can help me?
I don't have much experience with macros, I'm just doing some tests. And I'm not sure this is possible.
Code:
defmodule XMacro do
defmacro __using__(opts) do
quote do
def get_doc(function_name) do
Code.eval_string("#{unquote(opts)[:module]}.doc_#{function_name}")
end
end
end
end
defmodule Xxx do
@moduledoc """
Documentation for `Xxx`.
"""
use XMacro, module: XxxDescription
@doc get_doc(:hello)
def hello do
:world
end
end
defmodule XxxDescription do
def doc_hello do
"
Hello world.
## Examples
iex> Xxx.hello()
:world
"
end
end
With this code, I have the following error:
** (CompileError) lib/xxx.ex:8: undefined function get_doc/1 (there is no such import)
And after if I comment the code: @doc get_doc(:hello)
and put get_doc(:hello)
code inside the hello
function everything works...
I think my problem is because should use import
instead use
. Right?
Upvotes: 1
Views: 161
Reputation: 121010
While this is totally possible, I would strongly discourage you from doing this. This is unreadable, unmaintainable, extremely error-prone and there is zero reason for doing that. Docs are often (I’d say more often than in HTML) being read from source code. Also, I am unsure if doctests would be happy to see injected docs.
Before ever starting to deal with macros, one should clearly distinguish between compile time and runtime. elixir is a compiled language and one cannot call functions before the module exporting them is compiled.
In your code, get_doc/1
gets called during compile time, and your macro injects it into the same module, which is currently being compiled (and therefore functions from it cannot be called, they do not exist let alone exported yet.) An attempt to write macros without a clear understanding of the concept of what is compile-time, what is run-time, and how AST gets injected and expanded is something like shutting off a machine gun from a roller coaster. One might accidentally get to the target, but the collateral damage is hardly acceptable.
That said, if you are still into it, here is the code that would [most likely, I did not test it] work.
defmodule XDoc do
defmacro get_doc(module, function_name) do
quote do
unquote(module).unquote(:"doc_#{function_name}")()
end
end
end
defmodule XxxDescription do
def doc_hello, do: "Documentation"
end
defmodule Xxx do
import XDoc, only: [get_doc: 2]
@doc get_doc(XxxDescription, :hello)
def hello, do: :world
end
Upvotes: 2