Marin
Marin

Reputation: 1020

Elixir macro for write documentation

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

Answers (1)

Aleksei Matiushkin
Aleksei Matiushkin

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. 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

Related Questions