ELIXIRCHEATSHEET
ECOSYSTEM,
FUNDAMENTAL
&RUNTIME
BENKHALFALLAHHÉLA
1.
ECOSYSTEM
Elixir is a dynamic & functional language.
iex : Elixir's interactive shell.
mix : build automation tool.
hex : package manager for the Erlang ecosystem.
COMMAND DESCRIPTION
mix new my_project Create new project
mix compile Compile project
MIX_ENV=prod mix compile Compile by env
Available envs: prod, dev
& test (default is dev)
iex -S mix + recompile Run project & reload on
each change
mix release Create release
MIX_ENV=prod mix release Create release by env
mix deps.get Add a dependency
mix hex.outdated Show outdated
dependencies
mix hex.audit Audit dependencies
mix do deps.get, deps.compile Multiple tasks
DOCUMENTATION
ExDoc: generate documentation
mix docs
https://hex.pm/packages/ex_doc
Inch: documentation coverage
mix inch
https://github.com/rrrene/inch_ex
CODEQUALITY
ExUnit : unit test
mix test
Excoveralls : unit test coverage
MIX_ENV=test mix coveralls
https://github.com/parroty/excoveralls
TAG DESCRIPTION
@moduledoc Module documentation
@doc Function documentation
@spec
@spec function_name (type1,
type2) :: return_type
Types spec documentation
COMMAND DESCRIPTION
mix format Code formatter
credo & dialyxir
mix credo --strict
mix dialyzer
Static code analysis
2.
FUNDAMENTAL
FILESEXTENSION
NAMINGCONVENTION
File snake_case
Module UpperCamelCase
Function snake_case
Variable snake_case
Unused
parameter
_foo
Atoms snake_case or UpperCamelCase
:true, :is_connected?, :debit_type_card
.ex : elixir file
.exs : elixir scripts file
DEFINITION
MODULE
defmodule ElixirPatternMatching do
// staff
end
FUNCTION
def sum(x,y) do
IO.inspect x
IO.inspect y
x + y # returned value
end
In Elixir, a function must be defined inside a module.
PRIVATEFUNCTION
defp get_gps_coordinates(country) do
%{
France: {46.71109, 1.7191036},
Spain: {40.2085, -3.713},
Italy: {41.29246, 12.5736108}
}[country]
end
PIPEOPERATOR
"Elixir rocks"
|> String.upcase()
|> String.split()
TYPECHECKS
is_atom/1
is_bitstring/1
is_boolean/1
is_function/1
is_function/2
is_integer/1
is_float/1
is_binary/1
is_list/1
is_map/1
is_tuple/1
is_nil/1
is_number/1
is_pid/1
is_port/1
is_reference/1
https://hexdocs.pm/elixir/Kernel.html#is_atom/1
INTEGEROPERATIONS
import Integer
n = 12
n
|> digits()
|> IO.inspect # → [1, 2]
n
|> to_charlist()
|> IO.inspect # → '12'
n
|> to_string()
|> IO.inspect # → "12"
n
|> is_even()
|> IO.inspect # true
n
|> is_odd()
|> IO.inspect # false
https://hexdocs.pm/elixir/Integer.html#content
FLOATOPERATIONS
import Float
n = 10.3
n
|> ceil() # → 11.0
|> IO.inspect
n
|> to_string() # → "10.3"
|> IO.inspect
https://hexdocs.pm/elixir/Float.html#content
TYPECASTING
Float.parse("34.1") # → {34.1, ""}
Integer.parse("34") # → {34, ""}
Float.to_string(34.1) # → "3.4100e+01"
Float.to_string(34.1, [decimals: 2, compact: true]) # → « 34.1 »
https://hexdocs.pm/elixir/Integer.html#parse/2
https://hexdocs.pm/elixir/Float.html#parse/1
https://hexdocs.pm/elixir/Integer.html#to_string/1
https://hexdocs.pm/elixir/Float.html#to_string/1
STRING
import String
str = "hello"
str |> length() # → 5
str |> slice(2..-1) # → "llo"
str |> split(" ") # → ["hello"]
str |> capitalize() # → "Hello"
str |> match(regex)
Elixir string are UTF-8 encoded binaries, we can concatenate
string like this :
"hello" <> " " <> "world" => "hello world"
https://hexdocs.pm/elixir/String.html
LIST&ENUM
list_mixed = [3.14, :pie, "Apple"]
list_integer = [5, 1, 2, 3]
list_integer
|> Enum.map(fn x -> 2 * x end)
|> IO.inspect # [10, 2, 4, 6]
|> Enum.concat([11, 9, 22, 23])
|> IO.inspect # [10, 2, 4, 6, 11, 9, 22, 23]
|> Enum.filter(fn x -> x > 10 end)
|> IO.inspect. # [11, 22, 23]
|> Enum.sort(fn (a,b) -> a > b end)
|> IO.inspect # [23, 22, 11]
https://hexdocs.pm/elixir/List.html#content
https://hexdocs.pm/elixir/Enum.html#content
STREAM
Enum problem : each enum should complete before piping
result to next enum.
Solution : use stream !
stream =(
1..3
|> Stream.map(fn x -> IO.inspect(x) end)
|> Stream.map(fn x -> x * 2 end)
|> Stream.map(fn x -> IO.inspect(x) end)
)
Enum.to_list(stream)
1
2
2
4
3
6
Each number is completely evaluated before moving to the
next number in the enumeration !
Streams are lazy !
Streams are useful when working with large, possibly
infinite, collections.
TUPLE
Tuple are like a statically-sized arrays : they hold a fixed
number of elements. For dynamic length use List.
list = [1, 2, true, 3]
tuple = {1, 2, true, 3}
gps_coordinate = {46.71109, 1.7191036}
{latitude, longitude} = {46.71109, 1.7191036} # pattern matching
https://hexdocs.pm/elixir/Tuple.html#functions
MAP(KEY/VALUE)
def get_gps_coordinates(country) do
%{
France: {46.71109, 1.7191036},
Spain: {40.2085, -3.713},
Italy: {41.29246, 12.5736108}
}[country]
end
When keys are dynamics :
map = %{"foo" => "bar", "hello" => "world"}
When keys are constants :
%{foo: "bar", hello: "world"} or %{:foo => "bar", :hello => "world"}
https://devhints.io/elixir#map
https://hexdocs.pm/elixir/Map.html#functions
STRUCT
defmodule ElixirPatternMatching.Country do
defstruct name: "", code: ""
end
defmodule ElixirPatternMatching.Examples do
alias ElixirPatternMatching.Country
def get_all_countries do
[
%Country{name: "France", code: "FR"},
%Country{name: "Spain", code: "ES"},
%Country{name: "Italy", code: "IT"}
]
end
end
https://elixir-lang.org/getting-started/structs.html
PATTERNMATCHING
TUPLE
{latitude, longitude} = {46.71109, 1.7191036} # tuple
{_latitude, longitude} = {46.71109, 1.7191036} # unused latitude
{_, longitude} = {46.71109, 1.7191036} # skip latitude
ARRAY
[first_item, second_item, third_item] = [
%{name: "France", code: "FR"},
%{name: "Spain", code: "ES"},
%{name: "Italy", code: "IT"}
] # destructuring
[first_element | rest] = [
%{name: "France", code: "FR"},
%{name: "Spain", code: "ES"},
%{name: "Italy", code: "IT"}
] # collect the rest into an array
[head | tail] = [
%{name: "France", code: "FR"},
%{name: "Spain", code: "ES"},
%{name: "Italy", code: "IT"}
] # tail is an array
[first_item, _, third_item] # skip the second item
[first_item, _second_item, third_item] # skip the second item
[first_element | _] # skip the rest
MAP
%{name: name, writer: writer, date: date} = %{
name: "Elixir In Action",
writer: "Sasa Juric",
date: "2019"
}
https://medium.com/@helabenkhalfallah/elixir-pattern-matching-
bd4a1eb4d59f
MULTICLAUSEFUNCTIONS
MULTICLAUSE
def get_cars(%{type: "bmw"} = params) do
IO.puts("I want only #{params.type} !")
end
def get_cars(%{type: "volkswagen"} = params) do
IO.puts("I want only #{params.type} !")
end
# the default clause
def get_cars(_) do
IO.puts("I want all cars !")
end
Instead of having classical branching (if/else), we can do
multiple clauses for our function get_cars.
MULTICLAUSEWITHGUARD
def get_cars(category, %Car{type: "bmw"} = car) when
is_binary(category) do
IO.puts("I want only #{String.upcase(car.type)}
#{String.upcase(car.category)} !")
end
def get_cars(%Car{} = car) do
IO.puts("I want all cars !")
end
https://medium.com/@helabenkhalfallah/elixir-pattern-matching-
bd4a1eb4d59f
IF,CASE,COND
IF
Multiple lignes :
if condition do
...
else
...
end
Example :
if String.contains?(name, " ") do
split_name = name |> String.split(" ")
first_letter = split_name |> List.first() |> String.slice(0, 1)
last_letter = split_name |> List.last() |> String.slice(0, 1)
else
name |> String.slice(0, 1)
end
Single ligne :
if condition, do: something, else: another_thing
Example :
def max(a, b) do
if a >= b, do: a, else: b
end
COND
Definition :
cond do
expression_1 ->
...
expression_2 ->
...
end
Example :
def greet(lang) do
cond do
lang == "en" ->
"Hello"
lang == "fr" ->
"Bonjour"
lang == "es" ->
"Hola"
true ->
"We don't have a greeting for that."
end
end
cond will raise an error if there is no match. To handle this,
we should define a condition set to true.
CASE
Definition :
case expression do
pattern_1 ->
...
pattern_2 ->
...
end
Example :
def greet(lang) do
case lang do
"eng" -> "Hello"
"fr" -> "Bonjour"
"es" -> "Hola"
_ -> "We don't have a greeting for that."
end
end
Default clause ‘_’ is mandatory.
ERRORHANDLING
TRY,RESCUE,AFTER
try do
opts
|> Keyword.fetch!(:source_file)
|> File.read!()
rescue
e in KeyError -> IO.puts("missing :source_file option")
e in File.Error -> IO.puts("unable to read source file")
end
TRY,CATCH
try do
...
catch
type_pattern_1, error_value_1 -> …
type_pattern_2, error_value_2 -> ...
end
THROW
try do
for x <- 0..10 do
if x == 5, do: throw(x)
IO.puts(x)
end
catch
x -> IO.puts("Caught: #{x}")
end
The throw function gives us the ability to exit execution
with a specific value we can catch and use.
CREATENEWEXCEPTION
defmodule ExampleError do
defexception message: "an example error has occurred"
end
try do
raise ExampleError
rescue
e in ExampleError -> e
end
IMPORT,ALIAS&USE
defmodule Stats do
alias Math.List, as: List
# In the remaining module definition List expands to
Math.List.
end
defmodule ElixirPatternMatching.Country do
defstruct name: "", code: ""
end
defmodule ElixirPatternMatching.Examples do
alias ElixirPatternMatching.Country
# we can call Country directly without
ElixirPatternMatching.Country
def get_all_countries do
[
%Country{name: "France", code: "FR"},
%Country{name: "Spain", code: "ES"},
%Country{name: "Italy", code: "IT"}
]
end
end
import Integer # import all Integer functions
import String
import List, only: [duplicate: 2] # import only duplicate
import ElixirPatternMatching.Examples,
only: [
get_gps_coordinates: 1,
get_all_countries: 0,
get_cars: 2
]
https://elixirschool.com/en/lessons/basics/modules/#composition
https://elixir-lang.org/getting-started/alias-require-and-import.html
3.
RUNTIME
BEAM&PROCESS
Erlang VM is called Beam.
Beam is a garbage collector memory management.
- inside Beam VM, process are lightweights and
independents (isolated).
- process are also independents from the Host'OS
(deterministic system, the same behavior everywhere).
- process communicate only by exchanging messages.
- each process has its own memory (a mailbox, a heap and a
stack) and a process control block (PCB) with information
about the process.
- each process has its own heap means that each process's
heap is garbage collected independently. Only one
process will be paused for GC and not the whole runtime,
this is unlike Java JVM which stop the world for GC.
- supervisor to supervise process healthiness (restart
process if needed).
- process can supervise each other.
- schedulers to balance execution.
MOREINFORMATIONS:
https://blog.stenmans.org/theBeamBook/#_what_is_a_process
https://blog.stenmans.org/theBeamBook/
#_the_erlang_virtual_machine_beam
http://erlang.org/faq/academic.html#idp33095056

Elixir cheatsheet

  • 1.
  • 2.
    1. ECOSYSTEM Elixir is adynamic & functional language. iex : Elixir's interactive shell. mix : build automation tool. hex : package manager for the Erlang ecosystem. COMMAND DESCRIPTION mix new my_project Create new project mix compile Compile project MIX_ENV=prod mix compile Compile by env Available envs: prod, dev & test (default is dev) iex -S mix + recompile Run project & reload on each change mix release Create release MIX_ENV=prod mix release Create release by env mix deps.get Add a dependency mix hex.outdated Show outdated dependencies mix hex.audit Audit dependencies mix do deps.get, deps.compile Multiple tasks
  • 3.
    DOCUMENTATION ExDoc: generate documentation mixdocs https://hex.pm/packages/ex_doc Inch: documentation coverage mix inch https://github.com/rrrene/inch_ex CODEQUALITY ExUnit : unit test mix test Excoveralls : unit test coverage MIX_ENV=test mix coveralls https://github.com/parroty/excoveralls TAG DESCRIPTION @moduledoc Module documentation @doc Function documentation @spec @spec function_name (type1, type2) :: return_type Types spec documentation COMMAND DESCRIPTION mix format Code formatter credo & dialyxir mix credo --strict mix dialyzer Static code analysis
  • 4.
    2. FUNDAMENTAL FILESEXTENSION NAMINGCONVENTION File snake_case Module UpperCamelCase Functionsnake_case Variable snake_case Unused parameter _foo Atoms snake_case or UpperCamelCase :true, :is_connected?, :debit_type_card .ex : elixir file .exs : elixir scripts file
  • 5.
    DEFINITION MODULE defmodule ElixirPatternMatching do //staff end FUNCTION def sum(x,y) do IO.inspect x IO.inspect y x + y # returned value end In Elixir, a function must be defined inside a module. PRIVATEFUNCTION defp get_gps_coordinates(country) do %{ France: {46.71109, 1.7191036}, Spain: {40.2085, -3.713}, Italy: {41.29246, 12.5736108} }[country] end
  • 6.
    PIPEOPERATOR "Elixir rocks" |> String.upcase() |>String.split() TYPECHECKS is_atom/1 is_bitstring/1 is_boolean/1 is_function/1 is_function/2 is_integer/1 is_float/1 is_binary/1 is_list/1 is_map/1 is_tuple/1 is_nil/1 is_number/1 is_pid/1 is_port/1 is_reference/1 https://hexdocs.pm/elixir/Kernel.html#is_atom/1
  • 7.
    INTEGEROPERATIONS import Integer n =12 n |> digits() |> IO.inspect # → [1, 2] n |> to_charlist() |> IO.inspect # → '12' n |> to_string() |> IO.inspect # → "12" n |> is_even() |> IO.inspect # true n |> is_odd() |> IO.inspect # false https://hexdocs.pm/elixir/Integer.html#content
  • 8.
    FLOATOPERATIONS import Float n =10.3 n |> ceil() # → 11.0 |> IO.inspect n |> to_string() # → "10.3" |> IO.inspect https://hexdocs.pm/elixir/Float.html#content TYPECASTING Float.parse("34.1") # → {34.1, ""} Integer.parse("34") # → {34, ""} Float.to_string(34.1) # → "3.4100e+01" Float.to_string(34.1, [decimals: 2, compact: true]) # → « 34.1 » https://hexdocs.pm/elixir/Integer.html#parse/2 https://hexdocs.pm/elixir/Float.html#parse/1 https://hexdocs.pm/elixir/Integer.html#to_string/1 https://hexdocs.pm/elixir/Float.html#to_string/1
  • 9.
    STRING import String str ="hello" str |> length() # → 5 str |> slice(2..-1) # → "llo" str |> split(" ") # → ["hello"] str |> capitalize() # → "Hello" str |> match(regex) Elixir string are UTF-8 encoded binaries, we can concatenate string like this : "hello" <> " " <> "world" => "hello world" https://hexdocs.pm/elixir/String.html LIST&ENUM list_mixed = [3.14, :pie, "Apple"] list_integer = [5, 1, 2, 3] list_integer |> Enum.map(fn x -> 2 * x end) |> IO.inspect # [10, 2, 4, 6] |> Enum.concat([11, 9, 22, 23]) |> IO.inspect # [10, 2, 4, 6, 11, 9, 22, 23] |> Enum.filter(fn x -> x > 10 end) |> IO.inspect. # [11, 22, 23] |> Enum.sort(fn (a,b) -> a > b end) |> IO.inspect # [23, 22, 11] https://hexdocs.pm/elixir/List.html#content https://hexdocs.pm/elixir/Enum.html#content
  • 10.
    STREAM Enum problem :each enum should complete before piping result to next enum. Solution : use stream ! stream =( 1..3 |> Stream.map(fn x -> IO.inspect(x) end) |> Stream.map(fn x -> x * 2 end) |> Stream.map(fn x -> IO.inspect(x) end) ) Enum.to_list(stream) 1 2 2 4 3 6 Each number is completely evaluated before moving to the next number in the enumeration ! Streams are lazy ! Streams are useful when working with large, possibly infinite, collections.
  • 11.
    TUPLE Tuple are likea statically-sized arrays : they hold a fixed number of elements. For dynamic length use List. list = [1, 2, true, 3] tuple = {1, 2, true, 3} gps_coordinate = {46.71109, 1.7191036} {latitude, longitude} = {46.71109, 1.7191036} # pattern matching https://hexdocs.pm/elixir/Tuple.html#functions MAP(KEY/VALUE) def get_gps_coordinates(country) do %{ France: {46.71109, 1.7191036}, Spain: {40.2085, -3.713}, Italy: {41.29246, 12.5736108} }[country] end When keys are dynamics : map = %{"foo" => "bar", "hello" => "world"} When keys are constants : %{foo: "bar", hello: "world"} or %{:foo => "bar", :hello => "world"} https://devhints.io/elixir#map https://hexdocs.pm/elixir/Map.html#functions
  • 12.
    STRUCT defmodule ElixirPatternMatching.Country do defstructname: "", code: "" end defmodule ElixirPatternMatching.Examples do alias ElixirPatternMatching.Country def get_all_countries do [ %Country{name: "France", code: "FR"}, %Country{name: "Spain", code: "ES"}, %Country{name: "Italy", code: "IT"} ] end end https://elixir-lang.org/getting-started/structs.html PATTERNMATCHING TUPLE {latitude, longitude} = {46.71109, 1.7191036} # tuple {_latitude, longitude} = {46.71109, 1.7191036} # unused latitude {_, longitude} = {46.71109, 1.7191036} # skip latitude
  • 13.
    ARRAY [first_item, second_item, third_item]= [ %{name: "France", code: "FR"}, %{name: "Spain", code: "ES"}, %{name: "Italy", code: "IT"} ] # destructuring [first_element | rest] = [ %{name: "France", code: "FR"}, %{name: "Spain", code: "ES"}, %{name: "Italy", code: "IT"} ] # collect the rest into an array [head | tail] = [ %{name: "France", code: "FR"}, %{name: "Spain", code: "ES"}, %{name: "Italy", code: "IT"} ] # tail is an array [first_item, _, third_item] # skip the second item [first_item, _second_item, third_item] # skip the second item [first_element | _] # skip the rest MAP %{name: name, writer: writer, date: date} = %{ name: "Elixir In Action", writer: "Sasa Juric", date: "2019" } https://medium.com/@helabenkhalfallah/elixir-pattern-matching- bd4a1eb4d59f
  • 14.
    MULTICLAUSEFUNCTIONS MULTICLAUSE def get_cars(%{type: "bmw"}= params) do IO.puts("I want only #{params.type} !") end def get_cars(%{type: "volkswagen"} = params) do IO.puts("I want only #{params.type} !") end # the default clause def get_cars(_) do IO.puts("I want all cars !") end Instead of having classical branching (if/else), we can do multiple clauses for our function get_cars. MULTICLAUSEWITHGUARD def get_cars(category, %Car{type: "bmw"} = car) when is_binary(category) do IO.puts("I want only #{String.upcase(car.type)} #{String.upcase(car.category)} !") end def get_cars(%Car{} = car) do IO.puts("I want all cars !") end https://medium.com/@helabenkhalfallah/elixir-pattern-matching- bd4a1eb4d59f
  • 15.
    IF,CASE,COND IF Multiple lignes : ifcondition do ... else ... end Example : if String.contains?(name, " ") do split_name = name |> String.split(" ") first_letter = split_name |> List.first() |> String.slice(0, 1) last_letter = split_name |> List.last() |> String.slice(0, 1) else name |> String.slice(0, 1) end Single ligne : if condition, do: something, else: another_thing Example : def max(a, b) do if a >= b, do: a, else: b end
  • 16.
    COND Definition : cond do expression_1-> ... expression_2 -> ... end Example : def greet(lang) do cond do lang == "en" -> "Hello" lang == "fr" -> "Bonjour" lang == "es" -> "Hola" true -> "We don't have a greeting for that." end end cond will raise an error if there is no match. To handle this, we should define a condition set to true.
  • 17.
    CASE Definition : case expressiondo pattern_1 -> ... pattern_2 -> ... end Example : def greet(lang) do case lang do "eng" -> "Hello" "fr" -> "Bonjour" "es" -> "Hola" _ -> "We don't have a greeting for that." end end Default clause ‘_’ is mandatory.
  • 18.
    ERRORHANDLING TRY,RESCUE,AFTER try do opts |> Keyword.fetch!(:source_file) |>File.read!() rescue e in KeyError -> IO.puts("missing :source_file option") e in File.Error -> IO.puts("unable to read source file") end TRY,CATCH try do ... catch type_pattern_1, error_value_1 -> … type_pattern_2, error_value_2 -> ... end
  • 19.
    THROW try do for x<- 0..10 do if x == 5, do: throw(x) IO.puts(x) end catch x -> IO.puts("Caught: #{x}") end The throw function gives us the ability to exit execution with a specific value we can catch and use. CREATENEWEXCEPTION defmodule ExampleError do defexception message: "an example error has occurred" end try do raise ExampleError rescue e in ExampleError -> e end
  • 20.
    IMPORT,ALIAS&USE defmodule Stats do aliasMath.List, as: List # In the remaining module definition List expands to Math.List. end defmodule ElixirPatternMatching.Country do defstruct name: "", code: "" end defmodule ElixirPatternMatching.Examples do alias ElixirPatternMatching.Country # we can call Country directly without ElixirPatternMatching.Country def get_all_countries do [ %Country{name: "France", code: "FR"}, %Country{name: "Spain", code: "ES"}, %Country{name: "Italy", code: "IT"} ] end end
  • 21.
    import Integer #import all Integer functions import String import List, only: [duplicate: 2] # import only duplicate import ElixirPatternMatching.Examples, only: [ get_gps_coordinates: 1, get_all_countries: 0, get_cars: 2 ] https://elixirschool.com/en/lessons/basics/modules/#composition https://elixir-lang.org/getting-started/alias-require-and-import.html
  • 22.
    3. RUNTIME BEAM&PROCESS Erlang VM iscalled Beam. Beam is a garbage collector memory management. - inside Beam VM, process are lightweights and independents (isolated). - process are also independents from the Host'OS (deterministic system, the same behavior everywhere). - process communicate only by exchanging messages. - each process has its own memory (a mailbox, a heap and a stack) and a process control block (PCB) with information about the process. - each process has its own heap means that each process's heap is garbage collected independently. Only one process will be paused for GC and not the whole runtime, this is unlike Java JVM which stop the world for GC. - supervisor to supervise process healthiness (restart process if needed). - process can supervise each other. - schedulers to balance execution.
  • 23.