Value In Brief
All You Need To Know Explained In Brief

Elixir Cheatsheet - Elixir in Brief

Elixir programming languages

Elixir is a dynamic functional compiled language that runs over an Erlang Virtual Machine called BEAM.

Erlang and its BEAM is well known for running low-lattency, distributed and fault-tolerant applications.

Elixir was designed to take all that advantages in a modern coding language.

Elixir data types are immutable.

In Elixir a function is usually described with its arity (number of arguments), such as: is_boolean/1.

File Types

Compile and Run Elixir code

Elixir Conventions

Comments

Code Documentation

defmodule Math do
  @moduledoc """
  Provides math-related functions.

  ## Examples

      iex> Math.sum(1, 2)
      3
  """

  @spec sum(number, number) :: number
  @doc """
  Calculates the sum of two numbers.
  """
  def sum(a, b), do: a + b
end

h Math
h Math.sum

Elixir Special Unbound Variable

Elixir/Erlang Virtual Machine inspection

:c.memory
# [
#   total: 19262624,
#   processes: 4932168,
#   processes_used: 4931184,
#   system: 14330456,
#   atom: 256337,
#   atom_used: 235038,
#   binary: 43592,
#   code: 5691514,
#   ets: 358016
# ]

Interactive Elixir

Basic Types

Integer

Float

Atom

BitString

Tuple

List

Map

PID

Function

Reference

Port

Type Testing

Converting Types

Number Operators

Boolean Operators

Falsy in Elixir is false and nil, otherwise will be truthy.

It’s possible to compare different data types and that’s the sorting order: number < atom < reference < functions < port < pid < tuple < list < bit string.

Pipe Operator

The return of a function will be passed as the first argument to the following.

1..100 |>
  Stream.map(&(&1 * 3)) |>
  Stream.filter(&(rem(&1, 2) != 0)) |>
  Enum.sum
#=> 7500

Pattern Matching

In Elixir = sign is not just an assign operator, but a Match Operator.

This means that you assign variables from right side to the left based on patterns defined by the left one. If a pattern does not match a MatchError is raised.

This powerful tool is also used to decompose complex objects like tuples, lists, etc into smaller ones:

x = 1 #=> assign 1 to x
2 = x #=> ** (MatchError)
1 = x #=> match and does not assign anything

<<0, 1, x>> = <<0, 1, 2, 3>> #=> ** (MatchError)
<<0, 1, x::binary>> = <<0, 1, 2, 3>>
<<0, 1>> <> <<x::binary>> = <<0, 1, 2, 3>>
<<0, 1>> <> <<x, y>> = <<0, 1, 2, 3>>
<<0, 1>> <> <<x>> <> <<y>> = <<0, 1, 2, 3>>

"world" <> x = "hello" #=> ** (MatchError)
"he" <> x = "hello"

{x, y, z} = {1, 2} #=> ** (MatchError)
{} = {1, 2} #=> ** (MatchError)
{:a, :b} = {:b, :a} #=> ** (MatchError)
{x, y} = {1, 2}

first..last = 1..5

[x, 4] = [:a, 5] #=> ** (MatchError)
[] = [:a, 5] #=> ** (MatchError)
[:a, :b] = [:b, :a] #=> ** (MatchError)
[x, 4] = [:a, 4]

[x | y] = [] #=> ** (MatchError)
[x | y] = [1]
[x | y] = [1, 2, 3]

[a: x] = [b: 9] #=> ** (MatchError)
[a: x] = [a: 5]
[{:a, x}] = [a: 5]

%{a: x} = %{b: 5} #=> ** (MatchError)
%{} = %{a: 5} # empty map matches any map
%{a: x, b: 5} = %{b: 5, a: 7, c: 9}

defmodule User do
  defstruct name: "John", age: 27
end
john = %User{age: 29}
%User{name: name} = john
name #=> "John"

So in other words:

So variables and non variables behave differently with the match operator.

In order to assert an empty map you have to use a guard instead of pattern match, just like:

(
  fn m when map_size(m) == 0 ->
    "empty map"
  end
).(%{}) #=> "empty map"

Pin Operator

The Pin Operator ^ is used to treat variables the same way non variables with the match operator. In other words, the Pin Operator will evaluate the variable and use its value to restrict a pattern, preserving its original value.

x = 1 #=> assign 1 to x
^x = 1 #=> match x value with right side 1
^x = 2 #=> ** (MatchError)

Match Operator Limitation

You cannot make function calls on the left side of a match.

Custom Operators

You can customize an Elixir Operator like the following example:

1 + 2 #=> 3
defmodule WrongMath do
  def a + b do
    {a, b}
  end
end
import WrongMath
import Kernel, except: [+: 2]
1 + 2 #=> {1, 2}

Sigils

Available delimiters for Sigil: /, |, ", ', (, [, {, <.

~w(one two three) #=> ["one", "two", "three"]
~w(one two three)c #=> ['one', 'two', 'three']
~w(one two three)a #=> [:one, :two, :three]

Bit Strings

Performance for Bit Strings

cheap functions

expensive functions

Binaries

Binaries are 8 bits multiple Bit Strings. Binaries are 8 bits by default.

Performance for Binaries

cheap functions

expensive functions

Strings

String is a Binary of code points where all elements are valid characters. Strings are surrounded by double-quotes and are encoded in UTF-8 by default.

Performance for Strings

cheap functions expensive functions
byte_size("hello") String.length("Hello")

Tuples

Tuple is a list that is stored contiguously in memory.

Performance for Tuples

cheap functions expensive functions
- tuple_size({:ok, "hello"}) - put_elem({:ok, "hello"}, 1, "world")
- elem({:ok, "hello"}, 1)

Lists

Lists implements Enumerables protocol.

List is a linked list structure where each element points to the next one in memory. When subtraction just the first ocurrence will be removed.

Performance for Lists

cheap functions expensive functions
`[97 [1, 6, 9]]` => prepend
hd([1, 5, 7]) => head [1, 5] -- [9, 5] # [1] => subtraction first occurrences
tl([1, 5, 7]) => tail length([1, 4])
:foo in [:foo, :bar] #=> true => in operator

Char List

A Char List is a List of code points where all elements are valid characters. Char Lists are surrounded by single-quote and are usually used as arguments to some old Erlang code.

Performance for Char Lists

cheap functions expensive functions
`[?H ‘ello’]` => prepend
'hello' -- 'world' # 'hel' => subtraction first occurrences
length('Hello')
?l in 'hello' #=> true => in operator

Keyword Lists

Keyword list is a list of tuples where first elements are atoms. When fetching by key the first match will return. If a keyword list is the last argument of a function the square brackets [ are optional.

Performance for Keyword Lists

cheap functions expensive functions
`[{:a, 6} [b: 7]] # [a: 6, b: 7]` => prepend
[a: 5, b: 7] -- [a: 5] # [b: 7] => subtraction first ocurrences
([a: 5, a: 7])[:a] # 5 => fetch by key
length(a: 5, b: 7) => optional squared brackets [

Maps

Maps implements Enumerables protocol.

Map holds a key value structure.

Performance for Maps

cheap functions expensive functions
%{name: "Mary", age: 29}[:name] => fetch :name
%{name: "Mary", age: 29}.name => fetch :name short notation
`%{%{name: “Mary”, age: 29} age: 31}` => update value for existing key
map_size(%{name: "Mary"}) #=> 1 => map size

Structs

Structs are built in top of Map.

defmodule User do
  defstruct name: "John", age: 27
end
john = %User{} #=> %User{age: 27, name: "John"}
mary = %User{name: "Mary", age: 25} #=> %User{age: 25, name: "Mary"}
meg = %{john | name: "Meg"} #=> %User{age: 27, name: "Meg"}
bill = Map.merge(john, %User{name: "Bill", age: 23})

john.name #=> John
john[:name] #=> ** (UndefinedFunctionError) undefined function: User.fetch/2
is_map john #=> true
john.__struct__ #=> User
Map.keys(john) #=> [:__struct__, :age, :name]

Ranges

Ranges are Struct.

Protocols

Here are all native data types that you can use: Atom, BitString, Float, Function, Integer, List, Map, PID, Port, Reference, Tuple.

defprotocol Blank do
  @doc "Returns true if data is considered blank/empty"
  def blank?(data)
end

defimpl Blank, for: Integer do
  def blank?(_), do: false
end

defimpl Blank, for: List do
  def blank?([]), do: true
  def blank?(_),  do: false
end

defimpl Blank, for: Map do
  def blank?(map), do: map_size(map) == 0
end

defimpl Blank, for: Atom do
  def blank?(false), do: true
  def blank?(nil),   do: true
  def blank?(_),     do: false
end

Blank.blank?(0) #=> false
Blank.blank?([]) #=> true
Blank.blank?([1, 2, 3]) #=> false
Blank.blank?("hello") #=> ** (Protocol.UndefinedError)

Structs do not share Protocol implementations with Map.

defimpl Blank, for: User do
  def blank?(_), do: false
end

You can also implement a Protocol for Any. And in this case you can derive any Struct.

defimpl Blank, for: Any do
  def blank?(_), do: false
end

defmodule DeriveUser do
  @derive Blank
  defstruct name: "john", age: 27
end

Elixir built-in most common used protocols: Enumerable, String.Chars, Inspect.

Nested data Structures

users = [
  john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]},
  mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]}
]
users[:john].age #=> 27

users = put_in users[:john].age, 31
users = update_in users[:mary].languages, &List.delete(&1, "Clojure")

Enums and Streams

Lists and Maps are Enumerables.

Enum module perform eager operations, meanwhile Stream module perform lazy operations.

# eager Enum
1..100 |> Enum.map(&(&1 * 3)) |> Enum.sum #=> 15150

# lazy Stream
1..100 |> Stream.map(&(&1 * 3)) |> Enum.sum #=> 15150

do/end Keyword List and Block Syntax

In Elixir you can use either Keyword List syntax or do/end Block syntax:

sky = :gray

if sky == :blue do
  :sunny
else
  :cloudy
end

if sky == :blue, do: :sunny, else: :cloudy

if sky == :blue, do: (
  :sunny
), else: (
  :cloudy
)

Conditional Flows (if/else/case/cond)

if / else

sky = :gray
if sky == :blue, do: :sunny, else: :cloudy

unless / else

sky = :gray
unless sky != :blue, do: :sunny, else: :cloudy

case / when

sky = {:gray, 75}
case sky, do: (
  {:blue, _}         -> :sunny
  {_, t} when t > 80 -> :hot
  _                  -> :check_wheather_channel
)

On when guards short-circuiting operators &&, || and ! are not allowed.

cond

cond is equivalent as if/else-if/else statements.

sky = :gray
cond do: (
  sky == :blue -> :sunny
  true         -> :cloudy
)

The with macro

opts = %{width: 10, height: 20}
with {:ok, width} <- Map.fetch(opts, :width),
     {:ok, height} <- Map.fetch(opts, :height) do
  {:ok, width * height}
else
  :error ->
    {:error, :wrong_data}
end
#=> {:ok, 200}

Recursion

There is traditional no for loop in Elixir, due to Elixir immutability There is a macro for that it’s also called as Comprehension but it works differently from a traditional for loop. If you want a simple loop iteration you’ll need to use recursion:

defmodule Logger do
  def log(msg, n) when n <= 0, do: ()
  def log(msg, n) do
    IO.puts msg
    log(msg, n - 1)
  end
end
Logger.log("Hello World!", 3)
# Hello World!
# Hello World!
# Hello World!

In functional programming languages map and reduce are two major algorithm concepts. They can be implemented with recursion or using the Enum module.

reduce will reduces the array into a single element:

defmodule Math do
  def sum_list(list, sum \\ 0)
  def sum_list([], sum), do: sum
  def sum_list([head | tail], sum) do
    sum_list(tail, head + sum)
  end
end
Math.sum_list([1, 2, 3]) #=> 6

Enum.reduce([1, 2, 3], 0, &+/2) #=> 6

map modifies an existing array (new array with new modified values):

defmodule Math do
  def double([]), do: []
  def double([head | tail]) do
    [head * 2 | double(tail)]
  end
end
Math.double([1, 2, 3]) #=> [2, 4, 6]

Enum.map([1, 2, 3], &(&1 * 2)) #=> [2, 4, 6]

Comprehension -> the for loop

Comprehension is a syntax sugar for the very powerful for special form. You can have generators and filters in that.

You can iterate over Enumerable what makes so close to a regular for loop on other languages:

for n <- [1, 2, 3, 4], do: n * n
#=> [1, 4, 9, 16]

You can also iterate over multiple Enumerable and then create a combination between them:

for i <- [:a, :b, :c], j <- [1, 2], do:  {i, j}
#=> [a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]

You can pattern match each element:

values = [good: 1, good: 2, bad: 3, good: 4]
for {:good, n} <- values, do: n * n
#=> [1, 4, 16]

Generators use -> and they have on the right an Enumerable and on the left a pattern matchable element variable.

You can have filters to filter truthy elements:

for dir  <- [".", "/"],
    file <- File.ls!(dir),
    path = Path.join(dir, file),
    File.regular?(path) do
  "dir=#{dir}, file=#{file}, path=#{path}"
end
#=> ["dir=., file=README.md, path=./README.md", "dir=/, file=.DS_Store, path=/.DS_Store"]

You can use :into to change the return type:

for k <- [:foo, :bar], v <- 1..5, into: %{}, do: {k, v}
#=> %{bar: 5, foo: 5}
for k <- [:foo, :bar], v <- 1..5, into: [], do: {k, v}
#=> [foo: 1, foo: 2, foo: 3, foo: 4, foo: 5, bar: 1, bar: 2, bar: 3, bar: 4, bar: 5]

Anonymous Functions

add = fn a, b -> a + b end
add.(4, 5) #=> 9

We can have multiple clauses and guards inside functions.

calc = fn
  x, y when x > 0 -> x + y
  x, y -> x * y
end
calc.(-1, 6) #=> 5
calc.(4, 5) #=> 45

Modules And Named Functions

defmodule Math do
  def sum(a, b) when is_integer(a) and is_integer(b), do: a + b
end

Math.sum(1, 2) #=> 3

Math.__info__(:functions) #=> [sum: 2]

Module attribute works as constants, evaluated at compilation time:

defmodule Math do
  @foo :bar
  def print, do: @foo
end

Math.print #=> :bar

Special Module attributes:

Default Argument

defmodule Concat do
  def join(a, b, sep \\ " ") do
    a <> sep <> b
  end
end

IO.puts Concat.join("Hello", "world")      #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world

Default values are evaluated runtime.

defmodule DefaultTest do
  def dowork(x \\ IO.puts "hello") do
    x
  end
end
DefaultTest.dowork #=> :ok
# hello
DefaultTest.dowork 123 #=> 123
DefaultTest.dowork #=> :ok
# hello

Function with multiple clauses and a default value requires a function head where default values are set:

defmodule Concat do
  def join(a, b \\ nil, sep \\ " ") # head function

  def join(a, b, _sep) when is_nil(b) do
    a
  end

  def join(a, b, sep) do
    a <> sep <> b
  end
end

IO.puts Concat.join("Hello")               #=> Hello
IO.puts Concat.join("Hello", "world")      #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world

Function Capturing

& could be used with a module function.

When capturing you can use the function/operator with its arity.

&(&1 * &2).(3, 4) #=> 12
(&*/2).(3, 4) #=> 12

(&Kernel.is_atom(&1)).(:foo) #=> true
(&Kernel.is_atom/1).(:foo) #=> true
(&{:ok, [&1]}).(:foo) #=> {:ok, [:foo, :bar]}
(&[&1, &2]).(:foo, :bar) #=> [:foo, :bar]
(&[&1 | [&2]]).(:foo, :bar) #=> [:foo, :bar]

Behaviours

Behaviour modules defines functions

defmodule Parser do
  @callback parse(String.t) :: any
  @callback extensions() :: [String.t]
end

defmodule JSONParser do
  @behaviour Parser

  def parse(str), do: # ... parse JSON
  def extensions, do: ["json"]
end

Exceptions/Errors => raise/try/rescue

Exceptions/Errors in Elixir are Structs.

defmodule MyError do
  defexception message: "default message"
end

is_map %MyError{} #=> true
Map.keys %MyError{} #=> [:__exception__, :__struct__, :message]

raise MyError #=> ** (MyError) default message
raise MyError, message: "custom message" #=> ** (MyError) custom message

You can rescue an error with:

try do
  raise "oops"
rescue
  e in RuntimeError -> e
after
  IO.puts "I can do some clean up here"
end
#=> %RuntimeError{message: "oops"}

try do
  raise "oops"
rescue
  RuntimeError -> "Error!"
end
#=> "Error!"

throw/catch sometime is used for circuit breaking, but you can usually use another better way:

try do
  Enum.each -50..50, fn(x) ->
    if rem(x, 13) == 0, do: throw(x)
  end
  "Got nothing"
catch
  x -> "Got #{x}"
end
#=> "Got -39"

Enum.find -50..50, &(rem(&1, 13) == 0)
#=> -39

exit can be caught but this is rare in Elixir:

try do
  exit "I am exiting"
catch
  :exit, _ -> "not really"
end
#=> "not really"

You can ommit try inside functions and use rescue, catch, after directly:

def without_even_trying do
  raise "oops"
after
  IO.puts "cleaning up!"
end

IO

StringIO

File

{:ok, file} = File.open "my-file.md", [:write]
IO.binwrite file, "hello world"
File.close file
File.read "my-file.md" #=> {:ok, "hello world"}

Path

Processes, Tasks and Agents

Process in Elixir has the same concept as threads in a lot of other languages, but extremely lightweight in terms of memory and CPU. They are isolated from each other and communicate via message passing.

The idea is to have a supervisor that spawn_link new processes and when they fail the supervisor will restart them. This is the basics for Fail Fast and Fault Tolerant in Elixir.

Tasks are used in supervision trees.

parent = self()

spawn_link(fn -> send(parent, {:hello, self()}) end)
receive do: ({msg, pid} -> "#{inspect pid} => #{msg}"), after: (1_000 -> "nothing after 1s")

Task.start_link(fn -> send(parent, {:hello, self()}) end)
receive do: ({msg, pid} -> "#{inspect pid} => #{msg}"), after: (1_000 -> "nothing after 1s")

flush()

State can be stored in processes or using its abstraction: Agent.

Manual implementation of a storage using Elixir processes:

defmodule KV do
  def start_link do
    Task.start_link(fn -> loop(%{}) end)
  end

  defp loop(map) do
    receive do
      {:get, key, caller} ->
        send caller, Map.get(map, key)
        loop(map)
      {:put, key, value} ->
        loop(Map.put(map, key, value))
    end
  end
end
{:ok, pid} = KV.start_link

send pid, {:put, :hello, :world}
send pid, {:get, :hello, self()}
flush() #=> :world

Implementation of a storage using Agent:

{:ok, pid} = Agent.start_link(fn -> %{} end)
Agent.update(pid, fn map -> Map.put(map, :hello, :world) end)
Agent.get(pid, fn map -> Map.get(map, :hello) end)

alias, require, import and use

In order to facilitate code reuse Elixir has: alias, require, import (directives) and use (macro).

All modules are defines inside Elixir namespace but it can be omitted for convenience.

alias, require and import are lexically scoped, which means that it will be valid just inside the scope it was defined. This is not a global scope.

require is usually used to require Elixir macro code:

Integer.is_odd(3) #=> ** (CompileError): you must require Integer before invoking the macro Integer.is_odd/1
require Integer
Integer.is_odd(3) #=> true

use call __using__ when the module is being used:

defmodule Fruit do
  defmacro __using__(option: option) do
    IO.puts "options=#{inspect option}"
    quote do: IO.puts "Using Fruit module"
  end
end

defmodule Meal do
  use Fruit, option: :hello
end
#=> "Good to see you've added Fruit to your meal"

Meta Programming

quote do: 2 * 2 == 4
#=> {
#=>   :==,
#=>   [context: Elixir, import: Kernel],
#=>   [
#=>     {
#=>       :*,
#=>       [context: Elixir, import: Kernel],
#=>       [2, 2]
#=>     },
#=>     4
#=>   ]
#=> }

Erlang libraries

Elixir provider some Erlang modules as atoms.

That’s all. I hope it helps.