The Elixir Style Guide
Table of Contents
- Prelude
- Syntax
- Naming
- Modules
- Documentation
- Typespecs
- Structs
- Exceptions
- Collections
- Strings
- Regular Expressions
- Metaprogramming
- Testing
- Alternative Style Guides
- Tools
- Spread the Word
- Attribution
Prelude
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.
The Guide
This is our attempt at starting a community style guide for the Elixir programming language. Please feel free to make pull requests and contribute. We really want Elixir to have as vibrant of a community as any language that’s been around five times as long.
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:
Source Code Layout
- Use two spaces per indentation level. No hard tabs. [link]
# not preferred - four spaces
def some_function do
do_something
end
# preferred
def some_function do
do_something
end
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
- 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", <<"two">>, "three"], fn num -> IO.puts num end)
- Do not use spaces after non-word operators that only take one argument; or around the range operator. [link]
0 - 1 == -1
^pinned = some_func()
5 in 1..10
-
Use blank lines between
def
s to break up a function into logical paragraphs. [link]
def some_function(some_data) do
altered_data = Module.function(data)
end
def some_function do
result
end
def some_other_function do
another_result
end
def a_longer_function do
one
two
three
four
end
-
…but run single-line
def
s that match for the same function together. [link]
def some_function(nil), do: {:err, "No Value"}
def some_function([]), do: :ok
def some_function([first | rest]) do
some_function(rest)
end
-
If you use the
do:
syntax with functions and the line that makes up the function body is long, put thedo:
on a new line indented one level more than the previous line. [link]
def some_function(args),
do: Enum.map(args, fn(arg) -> arg <> " is on a very long line!" end)
When you use the convention above and you have more than one function clause
using the do:
syntax, put the do:
on a new line for each function clause:
# not preferred
def some_function([]), do: :empty
def some_function(_),
do: :very_long_line_here
# preferred
def some_function([]),
do: :empty
def some_function(_),
do: :very_long_line_here
-
If you have more than one multi-line
def
s do not use single-linedef
s. [link]
def some_function(nil) do
{:err, "No Value"}
end
def 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
-
Use the pipe operator (
|>
) to chain functions together. [link]
# not preferred
String.strip(String.downcase(some_string))
# preferred
some_string |> String.downcase |> String.strip
# Multiline pipelines are not further indented
some_string
|> String.downcase
|> String.strip
# Multiline pipelines on the right side of a pattern match
# should be indented on a new line
sanitized_string =
some_string
|> String.downcase
|> String.strip
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.
- Avoid using the pipe operator just once. [link]
# not preferred
some_string |> String.downcase
# preferred
String.downcase(some_string)
- Use bare variables in the first part of a function chain. [link]
# THE WORST!
# This actually parses as String.strip("nope" |> String.downcase).
String.strip "nope" |> String.downcase
# not preferred
String.strip(some_string) |> String.downcase |> String.codepoints
# preferred
some_string |> String.strip |> String.downcase |> String.codepoints
Syntax
-
Use parentheses when a
def
has arguments, and omit them when it doesn’t. [link]
# not preferred
def some_function arg1, arg2 do
# body omitted
end
def some_function() do
# body omitted
end
# preferred
def some_function(arg1, arg2) do
# body omitted
end
def some_function do
# body omitted
end
-
Never use
do:
for multi-lineif/unless
. [link]
# not preferred
if some_condition, do:
# a line of code
# another line of code
# note no end in this block
# preferred
if some_condition do
# some
# lines
# of code
end
-
Use
do:
for single lineif/unless
statements. [link]
# preferred
if some_condition, do: # some_stuff
-
Never use
unless
withelse
. Rewrite these with the positive case first. [link]
# not preferred
unless success? do
IO.puts 'failure'
else
IO.puts 'success'
end
# preferred
if success? do
IO.puts 'success'
else
IO.puts 'failure'
end
-
Use
true
as the last condition of thecond
special 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
- Never put a space between a function name and the opening parenthesis. [link]
# not preferred
f (3 + 2) + 1
# preferred
f(3 + 2) + 1
- Use parentheses in function calls, especially inside a pipeline. [link]
# not preferred
f 3
# preferred
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
- Omit parentheses in macro calls when a do block is passed. [link]
# not preferred
quote(do
foo
end)
# preferred
quote do
foo
end
- Optionally omit parentheses in function calls (outside a pipeline) when the last argument is a function expression. [link]
# preferred
Enum.reduce(1..10, 0, fn x, acc ->
x + acc
end)
# also preferred
Enum.reduce 1..10, 0, fn x, acc ->
x + acc
end
- Use parentheses for calls to functions with zero arity, so they can be distinguished from variables. Starting in Elixir 1.4, the compiler will warn you about locations where this ambiguity exists. [link]
defp do_stuff, do: ...
# not preferred
def my_func do
do_stuff # is this a variable or a function call?
end
# preferred
def my_func do
do_stuff() # this is clearly a function call
end
-
Indent and align successive
with
clauses. Put thedo:
argument on a new line, indented normally. [link]
with {:ok, foo} <- fetch(opts, :foo),
{:ok, bar} <- fetch(opts, :bar),
do: {:ok, foo, bar}
-
If the
with
expression has ado
block with more than one line, or has anelse
option, use multiline syntax. [link]
with {:ok, foo} <- fetch(opts, :foo),
{:ok, bar} <- fetch(opts, :bar) do
{:ok, foo, bar}
else
:error ->
{:error, :bad_arg}
end
Naming
-
Use
snake_case
for atoms, functions and variables. [link]
# not preferred
:"some atom"
:SomeAtom
:someAtom
someVar = 5
def someFunction do
...
end
def SomeFunction do
...
end
# preferred
:some_atom
some_var = 5
def some_function do
...
end
-
Use
CamelCase
for modules (keep acronyms like HTTP, RFC, XML uppercase). [link]
# not preferred
defmodule Somemodule do
...
end
defmodule Some_Module do
...
end
defmodule SomeXml do
...
end
# preferred
defmodule SomeModule do
...
end
defmodule SomeXML do
...
end
-
The names of predicate macros (compile-time generated functions that return a
boolean value) that can be used within guards should be prefixed with
is_
. For a list of allowed expressions, see the Guard docs. [link]
defmacro is_cool(var) do
quote do: unquote(var) == "cool"
end
-
The names of predicate functions that cannot be used within guards should
have a trailing question mark (
?
) rather than theis_
(or similar) prefix. [link]
def cool?(var) do
# Complex check if var is cool not possible in a pure function.
end
-
Private functions with the same name as public functions should start with
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)
Comments
Write expressive code and try to convey your program’s intention through control-flow, structure and naming. [link]
Use one space between the leading
#
character of the comment and the text of the comment. [link]
String.first(some_string) #not preferred
String.first(some_string) # preferred
- Comments longer than a word are capitalized, and sentences use punctuation. Use one space after periods. [link]
# not preferred
# these lowercase comments are missing punctuation
# preferred
# Capitalization example
# Use punctuation for complete sentences.
Comment Annotations
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}
- In cases where the problem is so obvious that any documentation would be redundant, annotations may be left at the end of the offending line with no note. This usage should be the exception and not the rule. [link]
start_task()
Process.sleep(5000) # FIXME
Use
TODO
to note missing features or functionality that should be added at a later date. [link]Use
FIXME
to note broken code that needs to be fixed. [link]Use
OPTIMIZE
to note slow or inefficient code that may cause performance problems. [link]Use
HACK
to note code smells where questionable coding practices were used and should be refactored away. [link]Use
REVIEW
to 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
README
or similar. [link]
Modules
Use one module per file unless the module is only used internally by another module (such as a test). [link]
Use
snake_case
file names forCamelCase
module names. [link]
# file is called some_module.ex
defmodule SomeModule do
end
- Represent each level of nesting within a module name as a directory. [link]
# file is called parser/core/xml_parser.ex
defmodule Parser.Core.XMLParser do
end
Don’t put a blank line after
defmodule
. [link]Put a blank line after module-level code blocks. [link]
List module attributes and directives in the following order: [link]
@moduledoc
@behaviour
use
import
alias
require
defstruct
@type
@module_attribute
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
alias My.Long.Module.Name
alias My.Other.Module.Name
require Integer
defstruct name: nil, params: []
@type params :: [{binary, binary}]
@module_attribute :foo
@other_attribute 100
...
end
-
Use the
__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
- If you want a prettier name for a module self-reference, set up an alias. [link]
defmodule SomeProject.SomeModule do
alias __MODULE__, as: SomeModule
defstruct [:name]
def name(%SomeModule{name: name}), do: name
end
Documentation
Documentation in Elixir (when read either in iex
with h
or generated with
ExDoc) uses the Module Attributes @moduledoc
and @doc
.
-
Always include a
@moduledoc
attribute in the line right afterdefmodule
in your module. [link]
# not preferred
defmodule SomeModule do
@moduledoc """
About the module
"""
...
end
defmodule AnotherModule do
use SomeModule
@moduledoc """
About the module
"""
...
end
# preferred
defmodule SomeModule do
@moduledoc """
About the module
"""
...
end
-
Use
@moduledoc false
if you do not intend on documenting the module. [link]
defmodule SomeModule do
@moduledoc false
...
end
-
Separate code after the
@moduledoc
with a blank line. [link]
# not preferred
defmodule SomeModule do
@moduledoc """
About the module
"""
use AnotherModule
end
# preferred
defmodule SomeModule do
@moduledoc """
About the module
"""
use AnotherModule
end
- Use heredocs with markdown for documentation. [link]
# not preferred
defmodule SomeModule do
@moduledoc "About the module"
end
defmodule 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
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).
-
Place
@typedoc
and@type
definitions 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
- If a union type is too long to fit on a single line, add a newline and indent with spaces to keep the return types aligned. [link]
# not preferred - no indentation
@type long_union_type :: some_type | another_type | some_other_type
| a_final_type
# preferred
@type long_union_type :: some_type | another_type | some_other_type
| a_final_type
# also preferred - one return type per line
@type long_union_type :: some_type
| another_type
| some_other_type
| a_final_type
-
Name the main type for a module
t
, for example: the type specification for a struct. [link]
defstruct name: nil, params: []
@type t :: %__MODULE__{
name: String.t | nil,
params: Keyword.t
}
- Place specifications right before the function definition, without separating them by a blank line. [link]
@spec some_function(term) :: result
def some_function(some_data) do
{:ok, some_data}
end
Structs
- If all the struct’s fields default to nil, supply them as a list of atoms. [link]
# not preferred
defstruct name: nil, params: nil
# preferred
defstruct [:name, :params]
- Indent additional lines of the struct definition, keeping the first keys aligned. [link]
defstruct foo: "test", bar: true, baz: false,
qux: false, quux: nil
Exceptions
-
Make exception names end with a trailing
Error
. [link]
# not preferred
defmodule BadHTTPCode do
defexception [:message]
end
defmodule BadHTTPCodeException do
defexception [:message]
end
# preferred
defmodule BadHTTPCodeError do
defexception [:message]
end
- Use lowercase error messages when raising exceptions, with no trailing punctuation. [link]
# not preferred
raise ArgumentError, "This is not valid."
# preferred
raise ArgumentError, "this is not valid"
Collections
No guidelines for collections have been added yet.
Strings
- Match strings using the string concatenator rather than binary patterns: [link]
# not preferred
<<"my"::utf8, _rest>> = "my string"
# preferred
"my" <> _rest = "my string"
Regular Expressions
No guidelines for regular expressions have been added yet.
Metaprogramming
- Avoid needless metaprogramming. [link]
Testing
- When writing ExUnit assertions, be consistent with the order of the expected and actual values under testing. Prefer placing the expected result on the right, unless the assertion is a pattern match. [link]
# preferred - expected result on the right
assert actual_function(1) == true
assert actual_function(2) == false
# not preferred - inconsistent order
assert actual_function(1) == true
assert false == actual_function(2)
# required - the assertion is a pattern match
assert {:ok, expected} = actual_function(3)
Alternative Style Guides
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.
Tools
Refer to Awesome Elixir for libraries and tools that can help with code analysis and style linting.
Getting Involved
Contributing
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 and code of conduct for more information.
Spread the Word
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.
Copying
License
This work is licensed under a Creative Commons Attribution 3.0 Unported License
Attribution
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 has kindly contributed to this project.