初学 elixir 时就被它方便的文档编写方式所吸引,我们可以这样编写模块的文档和函数的文档:
defmodule M4 do
@moduledoc """
Module doc for M4.
"""
@doc "function doc for f"
def f do
end
end
可以在 repl 里直接查看,也可以生成网页版的文档。
iex(2)> h M4
M4
Module doc for M4.
iex(3)> h M4.f
def f()
function doc for f
那么 elixir 究竟是如何从代码文件中获取到 doc 内容的呢。
Code.fetch_docs
标准库的 Code 模块里自带了很多用于处理源文件的函数,其中 Code.fetch_docs
可以直接获取一个模块里全部的 doc 内容:
iex(1)> Code.fetch_docs M4
{:docs_v1, 2, :elixir, "text/markdown", %{"en" => "Module doc for M4.\n"}, %{},
[{{:function, :f, 0}, 6, ["f()"], %{"en" => "function doc for f"}, %{}}]}
让我们来看看它的内部实现:
首先,它调用了 :code.get_object_code/1
函数来获取模块的 beam 文件的内容和路径。
iex(5)> :code.get_object_code M4
{M4,
<<70, 79, 82, 49, 0, 0, 4, 216, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 122,
0, 0, 0, 14, 9, 69, 108, 105, 120, 105, 114, 46, 77, 52, 8, 95, 95, 105, 110,
102, 111, 95, 95, 7, 99, 111, 109, 112, ...>>,
'.../_build/dev/lib/compile/ebin/Elixir.M4.beam'}
然后使用 :beam_lib.chunks/2
从 beam 文件中获取到 chunkref
为 'Docs' 的内容。
:beam_lib.chunks bin, ['Docs']
{:ok,
{M4,
[
{'Docs',
<<131, 80, 0, 0, 0, 165, 120, 156, 203, 96, 79, 97, 96, 79, 201, 79, 46,
142, 47, 51, 76, 100, 74, 97, 96, 75, 205, 201, 172, 200, 44, 202, 101,
96, 96, 224, 45, 73, 173, 40, 209, 207, 77, ...>>}
]}}
最后把获取到的内容转换成 erlang term 就行了:
iex(9)> :erlang.binary_to_term bin
{:docs_v1, 2, :elixir, "text/markdown", %{"en" => "Module doc for M4.\n"}, %{},
[{{:function, :f, 0}, 6, ["f()"], %{"en" => "function doc for f"}, %{}}]}
doc 内容是如何被编译到 beam 文件的
我们知道了 elixir 是如何从 beam 文件中获取到 doc 内容的,但 doc 内容又是如何从源代码被编译进 beam 文件的呢?最重要的是,@doc
里的内容是如何与每个函数关联起来的呢?
首先,和其它模块属性一样,我们调用 @doc "abc"
的时候是使用 @/1
macro 来设置了模块属性 :doc
的内容。
然后,模块属性的值会被存储到一个编译时的ets里,我们可以这样看到这个编译时的ets的内容:
@doc "function doc for g"
{set, bag} = :elixir_module.data_tables(__MODULE__)
IO.inspect(:ets.tab2list(set))
IO.inspect(:ets.tab2list(bag))
def g do
end
模块属性 @doc
和 @moduledoc
都是可覆盖的,也就是后面的定义会覆盖掉之前的值。
之后,elixir 编译器会调用 Module.compile_definition_attributes/6
这个内部函数,在定义新的函数时读取当前的 @doc
的值。
最后,生成好的函数签名(signature)会被存储到 data_tables 中,形如:
{{:function, :f, 0}, 6, [], "function doc for f", %{}},
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。