Reading — step 1 of 7
Learn
Elixir treats documentation as a first-class concern. Every project gets generated HTML docs from @moduledoc and @doc attributes. The Elixir docs themselves are a great example.
@moduledoc and @doc
defmodule MyApp.Math do
@moduledoc """
Math utilities for MyApp.
Provides addition, subtraction, and division with rounded outputs.
"""
@doc """
Adds two numbers.
## Examples
iex> MyApp.Math.add(2, 3)
5
iex> MyApp.Math.add(-1, 1)
0
"""
@spec add(number(), number()) :: number()
def add(a, b), do: a + b
end
@moduledoc— module-level documentation@doc— function-level documentation; goes ABOVEdef@spec— function type specification (used by Dialyzer)
Markdown in docs
Doc strings are Markdown:
@doc """
Formats a name.
## Options
* `:upcase` — convert to uppercase
* `:reverse` — reverse the characters
## Examples
iex> Format.name("ada", upcase: true)
"ADA"
## See also
- `String.upcase/1`
- `String.reverse/1`
"""
def name(s, opts \\ []), do: ...
Generated docs render this as proper HTML with sections, code blocks, links.
Doctests — examples that stay accurate
The ## Examples section's iex> lines are extracted as tests:
# test/my_app/math_test.exs
defmodule MyApp.MathTest do
use ExUnit.Case
doctest MyApp.Math # runs all iex> examples in MyApp.Math
end
The documentation can't drift from reality — broken examples fail the test suite.
Hiding implementation details
@doc false
def internal_helper(...), do: ...
@doc false excludes the function from generated docs. Different from @moduledoc false which hides the entire module.
Type specs and Dialyzer
@spec parse(String.t()) :: {:ok, integer()} | {:error, String.t()}
def parse(s), do: ...
@specdeclares the type signatureString.t()is the standard type alias for strings- Tools like Dialyzer use
@specfor static analysis
Common types:
String.t()/binary()— stringsinteger(),non_neg_integer(),pos_integer()float(),number()atom(),boolean()list(),[type],[a: integer]map(),%{key: type}tuple(),{type1, type2}keyword(),keyword(value_type)nil,term(),any(),none()
Custom types
defmodule User do
@type t :: %__MODULE__{
name: String.t(),
age: non_neg_integer(),
email: String.t() | nil
}
defstruct [:name, :age, :email]
end
The @type t is conventional for the "main type" exposed by the module. Other modules reference it as User.t().
ex_doc — generated HTML
Add to mix.exs deps:
{:ex_doc, "~> 0.30", only: :dev, runtime: false}
Then:
mix docs
Generates doc/index.html — beautifully formatted, like the official Elixir docs. Standard for any open-source Elixir library.
Hex.pm publishing
Libraries on Hex automatically display generated docs at hexdocs.pm. The combination of @doc + doctest + ex_doc + Hex makes Elixir's documentation story one of the strongest in any language.
Common mistakes
- No
@moduledoc— generated docs look unfinished. Add at least a one-liner. - Examples without doctest — they go stale and become misleading.
@doc falseon something useful — users can't find it but might still depend on it. Either expose properly or move to a private (defp) function.- Skipping
@spec— Dialyzer can't help; tooling can't suggest types. Add specs to public functions at minimum. - Generic types in @spec —
any()for everything defeats the purpose. Be specific.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…