A community driven style guide for Elixir
Liquid architecture. It's like jazz — you improvise, you work together, you play off each other, you make something, they make something.
—Frank Gehry
Style matters. Elixir has plenty of style but like all languages it can be stifled. Don't stifle the style.
This is community style guide for the Elixir programming language. Please feel free to make pull requests and suggestions, and be a part of Elixir's vibrant community.
If you're looking for other projects to contribute to please see the Hex package manager site.
Translations of the guide are available in the following languages:
Elixir v1.6 introduced a Code Formatter and Mix format task. The formatter should be preferred for all new projects and source code.
The rules in this section are applied automatically by the code formatter, but are provided here as examples of the preferred style.
Avoid trailing whitespace. [link]
End each file with a newline. [link]
Use Unix-style line endings (*BSD/Solaris/Linux/OSX users are covered by default, Windows users have to be extra careful). [link]
If you're using Git you might want to add the following configuration setting to protect your project from Windows line endings creeping in: [link]
git config --global core.autocrlf true
Limit lines to 98 characters. Otherwise, set the
:line_lengthoption in your
.formatter.exsfile. [link]
Use spaces around operators, after commas, colons and semicolons. Do not put spaces around matched pairs like brackets, parentheses, etc. Whitespace might be (mostly) irrelevant to the Elixir runtime, but its proper use is the key to writing easily readable code. [link]
sum = 1 + 2 {a, b} = {2, 3} [first | rest] = [1, 2, 3] Enum.map(["one", <>, "three"], fn num -> IO.puts(num) end)
0 - 1 == -1 ^pinned = some_func() 5 in 1..10
defs to break up a function into logical paragraphs. [link]
def some_function(some_data) do some_data |> other_function() |> List.first() enddef some_function do result end
def some_other_function do another_result end
def a_longer_function do one two
three four
end
defmodule. [link]
do:clause are too long to fit on the same line, put
do:on a new line, indented one level more than the previous line. [link]
def some_function([:foo, :bar, :baz] = args), do: Enum.map(args, fn arg -> arg <> " is on a very long line!" end)
When the
do:clause starts on its own line, treat it as a multiline function by separating it with blank lines.
# not preferred def some_function([]), do: :empty def some_function(_), do: :very_long_line_herepreferred
def some_function([]), do: :empty
def some_function(_), do: :very_long_line_here
# not preferred some_string = "Hello" |> String.downcase() |> String.trim() another_string <> some_stringpreferred
some_string = "Hello" |> String.downcase() |> String.trim()
another_string <> some_string
# also not preferred something = if x == 2 do "Hi" else "Bye" end String.downcase(something)preferred
something = if x == 2 do "Hi" else "Bye" end
String.downcase(something)
# not preferred [:first_item, :second_item, :next_item, :final_item]preferred
[ :first_item, :second_item, :next_item, :final_item ]
# not preferred list = [ :first_item, :second_item ]preferred
list = [ :first_item, :second_item ]
caseor
condclause needs more than one line (due to line length, multiple expressions in the clause body, etc.), use multi-line syntax for all clauses, and separate each one with a blank line. [link]
# not preferred case arg do true -> IO.puts("ok"); :ok false -> :error endnot preferred
case arg do true -> IO.puts("ok") :ok false -> :error end
preferred
case arg do true -> IO.puts("ok") :ok
false -> :error
end
String.first(some_string) # not preferredpreferred
String.first(some_string)
#character of the comment and the text of the comment. [link]
#not preferred String.first(some_string)preferred
String.first(some_string)
withclauses. Put the
do:argument on a new line, aligned with the previous clauses. [link]
with {:ok, foo}
withexpression has a
doblock with more than one line, or has an
elseoption, use multiline syntax. [link]
with {:ok, foo} {:error, :bad_arg} end
|>). [link]
# not preferred some_string |> String.downcase |> String.trimpreferred
some_string |> String.downcase() |> String.trim()
# not preferred f (3 + 2)preferred
f(3 + 2)
# not preferred f 3preferred
f(3)
not preferred and parses as rem(2, (3 |> g)), which is not what you want.
2 |> rem 3 |> g
preferred
2 |> rem(3) |> g
# not preferred some_function(foo, bar, [a: "baz", b: "qux"])preferred
some_function(foo, bar, a: "baz", b: "qux")
The rules in this section may not be applied by the code formatter, but they are generally preferred practice.
defs that match for the same function together, but separate multiline
defs with a blank line. [link]
def some_function(nil), do: {:error, "No Value"} def some_function([]), do: :okdef some_function([first | rest]) do some_function(rest) end
def, do not use single-line
defs. [link]
def some_function(nil) do {:error, "No Value"} enddef some_function([]) do :ok end
def some_function([first | rest]) do some_function(rest) end
def some_function([first | rest], opts) do some_function(rest, opts) end
# not preferred String.trim(String.downcase(some_string))preferred
some_string |> String.downcase() |> String.trim()
Multiline pipelines are not further indented
some_string |> String.downcase() |> String.trim()
Multiline pipelines on the right side of a pattern match
should be indented on a new line
sanitized_string = some_string |> String.downcase() |> String.trim()
While this is the preferred method, take into account that copy-pasting multiline pipelines into IEx might result in a syntax error, as IEx will evaluate the first line without realizing that the next line has a pipeline. To avoid this, you can wrap the pasted code in parentheses.
# not preferred some_string |> String.downcase()System.version() |> Version.parse()
preferred
String.downcase(some_string)
Version.parse(System.version())
# not preferred String.trim(some_string) |> String.downcase() |> String.codepoints()preferred
some_string |> String.trim() |> String.downcase() |> String.codepoints()
defhas arguments, and omit them when it doesn't. [link]
# not preferred def some_function arg1, arg2 do # body omitted enddef some_function() do # body omitted end
preferred
def some_function(arg1, arg2) do # body omitted end
def some_function do # body omitted end
do:for single line
if/unlessstatements. [link]
# preferred if some_condition, do: # some_stuff
unlesswith
else. Rewrite these with the positive case first. [link]
# not preferred unless success do IO.puts('failure') else IO.puts('success') endpreferred
if success do IO.puts('success') else IO.puts('failure') end
trueas the last condition of the
condspecial form when you need a clause that always matches. [link]
# not preferred cond do 1 + 2 == 5 -> "Nope"1 + 3 == 5 -> "Uh, uh" :else -> "OK"
end
preferred
cond do 1 + 2 == 5 -> "Nope"
1 + 3 == 5 -> "Uh, uh" true -> "OK"
end
defp do_stuff, do: ...not preferred
def my_func do # is this a variable or a function call? do_stuff end
preferred
def my_func do # this is clearly a function call do_stuff() end
snake_casefor atoms, functions and variables. [link]
# not preferred :"some atom" :SomeAtom :someAtomsomeVar = 5
def someFunction do ... end
preferred
:some_atom
some_var = 5
def some_function do ... end
CamelCasefor modules (keep acronyms like HTTP, RFC, XML uppercase). [link]
# not preferred defmodule Somemodule do ... enddefmodule Some_Module do ... end
defmodule SomeXml do ... end
preferred
defmodule SomeModule do ... end
defmodule SomeXML do ... end
is_. For a list of allowed expressions, see the Guard docs. [link]
defguard is_cool(var) when var == "cool" defguardp is_very_cool(var) when var == "very cool"
?) rather than the
is_(or similar) prefix. [link]
def cool?(var) do # Complex check if var is cool not possible in a pure function. end
do_. [link]
def sum(list), do: do_sum(list, 0)private functions
defp do_sum([], total), do: total defp do_sum([head | tail], total), do: do_sum(tail, head + total)
Write expressive code and try to convey your program's intention through control-flow, structure and naming. [link]
Comments longer than a word are capitalized, and sentences use punctuation. Use one space after periods. [link]
# not preferred # these lowercase comments are missing punctuationpreferred
Capitalization example
Use punctuation for complete sentences.
Annotations should usually be written on the line immediately above the relevant code. [link]
The annotation keyword is uppercase, and is followed by a colon and a space, then a note describing the problem. [link]
# TODO: Deprecate in v1.5. def some_function(arg), do: {:ok, arg}
start_task()FIXME
Process.sleep(5000)
TODOto note missing features or functionality that should be added at a later date. [link]
FIXMEto note broken code that needs to be fixed. [link]
OPTIMIZEto note slow or inefficient code that may cause performance problems. [link]
HACKto note code smells where questionable coding practices were used and should be refactored away. [link]
REVIEWto note anything that should be looked at to confirm it is working as intended. For example:
REVIEW: Are we sure this is how the client does X currently?[link]
Use other custom annotation keywords if it feels appropriate, but be sure to document them in your project's
READMEor similar. [link]
Use one module per file unless the module is only used internally by another module (such as a test). [link]
snake_casefile names for
CamelCasemodule names. [link]
# file is called some_module.exdefmodule SomeModule do end
# file is called parser/core/xml_parser.exdefmodule Parser.Core.XMLParser do end
@moduledoc
@behaviour
use
import
require
alias
@module_attribute
defstruct
@type
@callback
@macrocallback
@optional_callbacks
defmacro,
defmodule,
defguard,
def, etc.
Add a blank line between each grouping, and sort the terms (like module names) alphabetically. Here's an overall example of how you should order things in your modules:
defmodule MyModule do @moduledoc """ An example module """@behaviour MyBehaviour use GenServer import Something import SomethingElse require Integer alias My.Long.Module.Name alias My.Other.Module.Example @module_attribute :foo @other_attribute 100 defstruct [:name, params: []] @type params :: [{binary, binary}] @callback some_function(term) :: :ok | {:error, term} @macrocallback macro_name(term) :: Macro.t() @optional_callbacks macro_name: 1 @doc false defmacro __using__(_opts), do: :no_op @doc """ Determines when a term is `:ok`. Allowed in guards. """ defguard is_ok(term) when term == :ok @impl true def init(state), do: {:ok, state} # Define other functions here.
end
__MODULE__pseudo variable when a module refers to itself. This avoids having to update any self-references when the module name changes. [link]
defmodule SomeProject.SomeModule do defstruct [:name]def name(%__MODULE__{name: name}), do: name
end
defmodule SomeProject.SomeModule do alias __MODULE__, as: SomeModuledefstruct [:name] def name(%SomeModule{name: name}), do: name
end
# not preferred defmodule Todo.Todo do ... endpreferred
defmodule Todo.Item do ... end
Documentation in Elixir (when read either in
iexwith
hor generated with ExDoc) uses the Module Attributes
@moduledocand
@doc.
@moduledocattribute in the line right after
defmodulein your module. [link]
# not preferreddefmodule AnotherModule do use SomeModule
@moduledoc """ About the module """ ...
end
preferred
defmodule AThirdModule do @moduledoc """ About the module """
use SomeModule ...
end
@moduledoc falseif you do not intend on documenting the module. [link]
defmodule SomeModule do @moduledoc false ... end
@moduledocwith a blank line. [link]
# not preferred defmodule SomeModule do @moduledoc """ About the module """ use AnotherModule endpreferred
defmodule SomeModule do @moduledoc """ About the module """
use AnotherModule
end
# not preferred defmodule SomeModule do @moduledoc "About the module" enddefmodule SomeModule do @moduledoc """ About the module
Examples: iex> SomeModule.some_function :result """
end
preferred
defmodule SomeModule do @moduledoc """ About the module
## Examples iex> SomeModule.some_function :result """
end
Typespecs are notation for declaring types and specifications, for documentation or for the static analysis tool Dialyzer.
Custom types should be defined at the top of the module with the other directives (see Modules).
@typedocand
@typedefinitions together, and separate each pair with a blank line. [link]
defmodule SomeModule do @moduledoc false@typedoc "The name" @type name :: atom @typedoc "The result" @type result :: {:ok, term} | {:error, term} ...
end
# not preferred @type long_union_type :: some_type | another_type | some_other_type | one_more_type | a_final_typepreferred
@type long_union_type :: some_type | another_type | some_other_type | one_more_type | a_final_type
t, for example: the type specification for a struct. [link]
defstruct [:name, params: []]@type t :: %MODULE{ name: String.t() | nil, params: Keyword.t() }
@doc, without separating them by a blank line. [link]
@doc """ Some function description. """ @spec some_function(term) :: result def some_function(some_data) do {:ok, some_data} end
nil, followed by the other keywords. [link]
# not preferred defstruct name: nil, params: nil, active: truepreferred
defstruct [:name, :params, active: true]
defstructis a keyword list. [link]
# not preferred defstruct [params: [], active: true]preferred
defstruct params: [], active: true
required - brackets are not optional, with at least one atom in the list
defstruct [:name, params: [], active: true]
defstruct foo: "test", bar: true, baz: false, qux: false, quux: 1
If a multiline struct requires brackets, format it as a multiline list:
defstruct [ :name, params: [], active: true ]
Error. [link]
# not preferred defmodule BadHTTPCode do defexception [:message] enddefmodule BadHTTPCodeException do defexception [:message] end
preferred
defmodule BadHTTPCodeError do defexception [:message] end
# not preferred raise ArgumentError, "This is not valid."preferred
raise ArgumentError, "this is not valid"
# not preferred some_value = [{:a, "baz"}, {:b, "qux"}]preferred
some_value = [a: "baz", b: "qux"]
# not preferred %{:a => 1, :b => 2, :c => 0}preferred
%{a: 1, b: 2, c: 3}
# not preferred %{"c" => 0, a: 1, b: 2}preferred
%{:a => 1, :b => 2, "c" => 0}
# not preferred <> = "my string"preferred
"my" <> _rest = "my string"
No guidelines for regular expressions have been added yet.
# preferred assert actual_function(1) == truenot preferred
assert true == actual_function(1)
required - the assertion is a pattern match
assert {:ok, expected} = actual_function(3)
Aleksei Magusev's Elixir Style Guide — An opinionated Elixir style guide stemming from the coding style practiced in the Elixir core libraries. Developed by Aleksei Magusev and Andrea Leopardi, members of Elixir core team. While the Elixir project doesn't adhere to any specific style guide, this is the closest available guide to its conventions.
Credo's Elixir Style Guide — Style Guide for the Elixir language, implemented by Credo static code analysis tool.
Refer to Awesome Elixir for libraries and tools that can help with code analysis and style linting.
It's our hope that this will become a central hub for community discussion on best practices in Elixir. Feel free to open tickets or send pull requests with improvements. Thanks in advance for your help!
Check the contributing guidelines for more information.
A community style guide is meaningless without the community's support. Please tweet, star, and let any Elixir programmer know about this guide so they can contribute.
This work is licensed under a
Creative Commons Attribution 3.0 Unported License
The structure of this guide, bits of example code, and many of the initial points made in this document were borrowed from the Ruby community style guide. A lot of things were applicable to Elixir and allowed us to get some document out quicker to start the conversation.
Here's the list of people who have kindly contributed to this project.