ELIXIR
PROGRAMMING LANGUAGE
|> Lack of memory -> Accurate manual allocation, but lost pieces of
memory + error-prone
|> CPU becomes faster -> GC (automatic memory management)
|> CPU doesn’t become faster, but with more cores –> Concurrent
execution, but shared resources
|> Synchronisation -> Hard to code + performance penalty
|> Better concurrent model and execution environment -> Erlang VM
(perfect for distributed software), but lack of metaprogramming,
polymorphism and first-class tooling in ecosystem
HISTORY
ELIXIR IS GREAT FOR WRITING
HIGHLY PARALLEL, RELIABLE
APPLICATIONS.
Jose Valim
ELIXIR SUPPORTED BY IDIOMS AND CONVENTIONS
|> Easy Concurrent (Actor Model)
|> Fault Tolerant
|> Functional Paradigm
|> Extensible & DSL
|> Metaprogramming
|> Leverage all Erlang legacy
ELIXIR
Immutable data is known data
|> No way to concurrence data mess
|> Because handle state gracefully
IMMUTABILITY
Performance Implications
|> Copying Data
|> Garbage Collection (per process)
TYPES
FUNCTION
RANGEATOMFLOATINTEGERBOOLEAN
PID REF
LIST TUPLE KEYWORD
LIST MAP BINARY
|> Integers Unlimited
|> Floating-Point Numbers IEEE 754
|> Only Integer Ranges
VALUE TYPES
1
# > 1
1_000_000_000
# > 1000000000
3.14
# > 3.14
2.99792458e8
# > 299792458.0
:atom
# > :atom
:"molecula"
# > :molecula
1..10
# > 1..10
true == :true
# > true
false == :false
# > true
nil
# > nil
~r/[aeiou]/
# > ~r/[aeiou]/
PATTERN MATCHING
list = [1, 2, 3]
# > [1, 2, 3]
[_, teas] = [3, ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]]
# > [3, ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]]
teas
# > ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]
[tea, tea] = [1, 2]
PATTERN MATCHING
list = [1, 2, 3]
# > [1, 2, 3]
[_, teas] = [3, ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]]
# > [3, ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]]
teas
# > ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]
[tea, tea] = [1, 2]
# > ** (MatchError) no match of right hand side value: [1, 2]
PATTERN MATCHING
list = [1, 2, 3]
# > [1, 2, 3]
[_, teas] = [3, ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]]
# > [3, ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]]
teas
# > ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]
[tea, tea] = [1, 2]
tea = "Puer"
# > "Puer"
"Puer"
# > "Puer"
tea = "Da Hong Pao"
# > "Da Hong Pao"
"Da Hong Pao"
# > "Da Hong Pao"
^tea = "Longjing"
# > ** (MatchError) no match of right hand side value: "Longjing"
# > ** (MatchError) no match of right hand side value: [1, 2]
SYSTEM TYPES
self
# > #PID<0.80.0>
pid = spawn fn -> "Hi" end
# > #PID<0.84.0>
COLLECTION TYPES
{1, :atom, "string", {:another, :tuple}}
# > {1, :atom, "string", {:another, :tuple}}
[1, :atom, "string", [:another, :list]]
# > [1, :atom, "string", [:another, :list]]
[first_name: "Menya", last_name: "Musashi"]
# > [first_name: "Menya", last_name: "Musashi"]
f = fn args -> "#{Keyword.get(args, :first_name)} #{Keyword.get(args, :last_name)}" end
# > #Function<6.52032458/1 in :erl_eval.expr/5>
f.(first_name: "Menya", last_name: "Musashi") #
# > "Menya Musashi"
%{1 => :atom, "string" => %{ another: :map}, "exp" <> "ressions" => 2 + 2}
# > %{1 => :atom, "expressions" => 4, "string" => %{another: :map}}
%{"key" => "value1", "key" => "value2"}
# > %{"key" => "value2"}
m = %{some_key: "some_value"}
# > %{some_key: "some_value"}
m[:some_key]
# > "some_value"
m.some_key # shortcut for getting value by symbol key
# > "some_value"
<<1, 2>> # 0000000100000010 – 2 bytes (by default)
# > <<1, 2>>
bitstring = <<1 :: size(2), 2 :: size(4)>> # 00010010 – 1 byte
# > <<18::size(6)>>
bit_size(bitstring)
# > 6
byte_size(bitstring)
# > 1
STRINGS
#{}N T XUTF-8 HEREDOC
"""
HERE
DOC
"""
# > " HEREn DOCn"
SIGIL
~c/abc/
# > 'abc'
~r/a.+z/
# > ~r/a.+z/
~s/"Menya Musashi"/
# > ""Menya Musashi""
~w<Something for nothing>
# > ["Something", "for", "nothing"]
~w[Something for nothing]s
# > ["Something", "for", "nothing"]
~w{Something for nothing}a
# > [:Something, :for, :nothing]
~w|Something for nothing|c
# > ['Something', 'for', 'nothing']
BINARIES
|> Bits List
b = <<1, 2, 3>> # 00000001 00000010 00000011
# > <<1, 2, 3>>
byte_size(b)
# > 3
bit_size(b)
# > 24
b = <<1::size(2), 2::size(3)>>
# > <<10::size(5)>>
byte_size(b)
# > 1
bit_size(b)
# > 5
<<16245::utf8>>
# > "㽵"
<<16245::unsigned-integer-size(12)>>
# > <<247, 5::size(4)>>
<<sign::size(1), exp::size(11), mantissa::size(52)>> =
<<3.14159::float>>
# > <<64, 9, 33, 249, 240, 27, 134, 110>>
(1 + mantissa / :math.pow(2, 52)) * :math.pow(2, exp - 1023)
# > 3.14159
STRINGS AS BINARIES
UTF-8 BINARY UTF-8 BINARY UTF-8 BINARY UTF-8 BINARY
String.length("㽵")
# > 1
byte_size("㽵")
# > 3
String.codepoints("Nou0308l")
# > ["N", "o", "̈", "l"]
String.graphemes("Nou0308l")
# > ["N", "ö", "l"]
to_string(<<97, 98, 99>>)
# > "abc"
<<97, 98, 99>>
# > “abc"
is_binary("abc")
# > true
CHARACTER LISTS
'abc'
# > 'abc'
List.to_tuple('abc')
# > {97, 98, 99}
[97, 98, 99]
# > 'abc'
?a
# > 97
is_list('abc')
# > true
|> In practice, char lists are used mostly when interfacing
with Erlang, in particular old libraries that do not accept
binaries as arguments
OPERATORS
"Menya" <> " Musashi"
# > "Menya Musashi"
<<97>> <> "bc"
# > "abc"
[2, "Puer"] ++ [4, "Da Hong Pao"]
# > [2, "Puer", 4, "Da Hong Pao"]
"Tieguanyin" in ["Tieguanyin", "Maofeng", "Biluochun"]
# > true
"Gyokuro" in ["Tieguanyin", "Maofeng", "Biluochun"]
# > false
DICTIONARIES
kwlist = [ tea: "Maofeng", tea: "Tieguanyin", fruit: "Apple" ]
# > [tea: "Maofeng", tea: "Tieguanyin", fruit: "Apple"]
kwlist[:tea]
# > "Maofeng"
map = %{ "alpha" => 1, "betta" => 2 }
# > %{"alpha" => 1, "betta" => 2}
map["alpha"]
# > 1
map["gamma"]
# > nil
map = %{ tea: "Maofeng", tea: "Tieguanyin", fruit: "Apple" }
# > %{fruit: "Apple", tea: "Tieguanyin"}
map.tea
# > "Tieguanyin"
map[:tea]
# > "Tieguanyin"
map[:juice]
# > nil
defmodule Tea do
defstruct name: "Puer", type: :fermented, area: "Yunnan"
end
%Tea{}
# > %Tea{area: "Yunnan", name: "Puer", type: :fermented}
tea = %Tea{name: "Tieguanyin", area: "Fujian"}
# > %Tea{area: "Fujian", name: "Tieguanyin", type: :fermented}
tea.name
# > "Tieguanyin"
MAP
KEYWORD
LIST
STRUCT
COMPREHENSIONS
MAPGENERATOR FILTER
list = [1, 2, 3, 4, 5, 6, 7, 8]
# > [1, 2, 3, 4, 5, 6, 7, 8]
for x <- list, y <- list, x >= y, rem(x * y, 10) == 0, do: {x, y}
# > [{5, 2}, {5, 4}, {6, 5}, {8, 5}]
for x <- ~w{b c}, into: %{ "a" => "A" }, do: { x, String.upcase(x) }
# > %{"a" => "A", "b" => "B", "c" => "C"}
ANONYMOUS FUNCTIONS
f = fn (parameter list) -> body end
f = fn (a, b) -> a + b end
# > #Function<12.52032458/2 in :erl_eval.expr/5>
f.(1, 2)
# > 3
swap = fn [a, b] -> [b, a] end
# > #Function<6.52032458/1 in :erl_eval.expr/5>
swap.([1, 2])
# > [2, 1]
foobar = fn
(0, 0) -> "FooBar"
(0, _) -> "Foo"
(_, 0) -> "Bar"
(_, _) -> "Nothing"
end
# > #Function<12.52032458/2 in :erl_eval.expr/5>
foobar.(1, 0)
# > "Bar"
f = fn -> fn -> "A" end end
# > #Function<20.52032458/0 in :erl_eval.expr/5>
f.().()
# > "A"
add = &(&1 + &2)
# > &:erlang.+/2
add.(1, 2)
# > 3
multiplier = 10
# > 10
Enum.map [1, 2, 3, 4], &(&1 * multiplier)
# > [10, 20, 30, 40]
NAMED FUNCTIONS
defmodule Tea do
def brew(tea, water_temperature  90)
def brew(tea, water_temperature) when tea == "Gyokuro" do
"Made in Japan. Brewing #{tea} (#{water_temperature} C)..."
end
def brew(tea, water_temperature) when tea == "Tieguanyin" do
"Made in Chaina. Brewing #{tea} (#{water_temperature} C)..."
end
def brew(tea, water_temperature) do
"Brewing #{tea} (#{water_temperature} C)..."
end
defp think(topic) do
"Thinking about #{topic}..."
end
end
Tea.brew("Puer")
# > "Brewing Puer (90 C)..."
Tea.brew("Gyokuro")
# > "Made in Japan. Brewing Gyokuro (90 C)..."
Tea.brew("Gyokuro", 60)
# > "Made in Japan. Brewing Gyokuro (60 C)..."
Tea.brew("Tieguanyin", 80)
# > "Made in Chaina. Brewing Tieguanyin (80 C)..."
Tea.think("Eternity") # Check spelling
# > ** (UndefinedFunctionError) function Tea.think/1 is
undefined or private
# > Tea.think("Eternity")
RECURSION
defmodule MyList do
def sum([]), do: 0
def sum([head | tail]), do: head + sum(tail)
end
list = [3, 2, 1]
# > [3, 2, 1]
sum = MyList.sum(list)
# > 6
|> Reducing algorithm
|> Mapping algorithm
PIPE OPERATOR
defmodule Tea do
defmodule Master do
def brew(tea), do: "Brewing #{tea} with optimal water temperature..."
end
def find(title), do: title
end
Tea.Master.brew(Tea.find("Puer"))
# > "Brewing Puer with optimal water temperature..."
"Puer" |> Tea.find |> Tea.Master.brew
# > "Brewing Puer with optimal water temperature..."
prepend_awesome = fn x -> "Awesome " <> x end
# > #Function<6.52032458/1 in :erl_eval.expr/5>
append_try_it = fn x -> x <> ", try it!" end
# > #Function<6.52032458/1 in :erl_eval.expr/5>
"Elixir" |> prepend_awesome |> append_try_it # Exception
# > ** (CompileError) iex:8: undefined function append_try_it/1
# > (elixir) expanding macro: Kernel.|>/2
# > iex:8: (file)
"Elixir" |> prepend_awesome.() |> append_try_it.()
# > "Awesome Elixir, try it!"
FROM SOURCE CODE TO VIRTUAL MACHINE
.EXS
.EX
ELIXIR
COMPILER
BYTECODE
.BEAM
HDD
ERLANG VM
MODULES
defmodule Tea.Container do
def prepare do
IO.puts "Prepare container..."
end
end
defmodule Tea.Pot do
@material "yixing clay" # mostly use as a constant, can't be
defined inside function definition
def prepare do
IO.puts "Prepare pot from #{@material}..."
end
end
defmodule Tea.Bowl do
def prepare do
IO.puts "Prepare bowl..."
end
end
defmodule Tea.Spoon do
def prepare do
IO.puts "Prepare spoon..."
end
end
defmodule Tea.Utensils do
alias Tea.Container
alias Tea.{Pot, Bowl}
alias Tea.Spoon, as: Scoop
def prepare do
Container.prepare
Pot.prepare
Bowl.prepare
Scoop.prepare
end
end
defmodule Tea.Master do
def prepare(utensils) do
utensils.prepare
end
def choose_tea do
IO.puts "Choose tea sort..."
end
def brew do
IO.puts "Brew tea..."
end
def taste do
IO.puts "Thruuup-thruup... so tasty!"
end
end
defmodule Tea.Style do
defmacro style(name) do
quote do
IO.puts "Tea story: #{unquote(name)} style"
end
end
end
defmodule Tea.Ceremony do
import Tea.Style
alias Tea.Master
alias Tea.Utensils
style :chaineese
def run do
Master.prepare(Utensils)
Master.choose_tea
Master.brew
Master.taste
end
end
Tea.Ceremony.run
# > Prepare container...
# > Prepare pot from yixing clay...
# > Prepare bowl...
# > Prepare spoon...
# > Choose tea sort...
# > Brew tea...
# > Thruuup-thruup... so tasty!
# > :ok
|> Duck Typing
|> Type = Primitive implementation + Modules
|> @type
|> @spec
TYPE SUMMARY
PROTOCOLS
defprotocol CaesarCypher do
def encrypt(stringlike, shift)
def rot13(stringlike)
end
defimpl CaesarCypher, for: List do
def encrypt([], _shift), do: []
def encrypt([head | tail], shift) do
[rem((head - 97 + shift), 26) + 97 | encrypt(tail, shift)]
end
def rot13(list), do: encrypt(list, 13)
end
defimpl CaesarCypher, for: BitString do
def encrypt(string, shift) do
to_charlist(string) |> _encrypt(shift) |> to_string
end
def rot13(string), do: encrypt(string, 13)
defp _encrypt([], _shift), do: []
defp _encrypt([head | tail], shift) do
[rem((head - 97 + shift), 26) + 97 | _encrypt(tail, shift)]
end
end
CaesarCypher.encrypt('zappa', 1)
# > 'abqqb'
CaesarCypher.rot13('zappa')
# > 'mnccn'
CaesarCypher.encrypt("zappa", 1)
# > "abqqb"
CaesarCypher.rot13("zappa")
# > "mnccn"
|> Elixir is lexically scoped
|> Basic unit – function body
|> Module has it’s own non visible for functions scope
|> Comprehensions and with create a separate lexical scope
SCOPES
MACROS
|> Language extension
|> Never use a macro when you can use a function
ELIXIR
SOURCE
CODE
ELIXIR
MACRO
EXPANSION
ELIXIR
COMPILER
COMPILE TIME
BEAM
BYTECODE
CORE
ERLANG
MACROS
defmodule Meta do
defmacro define_methods(names) do
IO.puts("Compile time: #{inspect(names)}")
for name <- names do
quote do
def unquote(name)() do
IO.puts("Run time: #{inspect(unquote(name))}")
"Hi, from #{unquote(name)} method"
end
end
end
end
end
defmodule M do
import Meta
define_methods [:alpha, :beta]
end
# > Compile time: [:alpha, :beta]
M.alpha
# > Run time: :alpha
# > "Hi, from alpha method"
M.beta
# > Run time: :beta
# > "Hi, from beta method"
MIX
Build tool
|>Elixir built-in
|>Create application
|>Compile application
|>Test application (ExUnit)
|>Manage application dependencies
|>Environments (MIX_ENV: dev, test, prod)
mix new tea_master
mix deps.get
mix phoenix.new
What about concurrency?
PROCESSES
|> Lightweight
|> Isolated
|> Actors
P1
MESSAGE
MESSAGE
THREAD
P2
PROCESS IS NOT ONLY ABOUT
CONCURRENCY, BUT ALSO ABOUT
DISTRIBUTION AND FAULT-TOLERANCE.
Jose Valim
FAIL FAST, ALLOW SUPERVISOR TO RESTART OR BURY FAILED PROCESS
PROCESSES
P1 P2
p1 = spawn(fn ->
end)
# > #PID<0.86.0>
Process.alive?(p1)
# > false
p2 = self()
# > #PID<0.95.0>
Process.alive?(p2)
# > true
PROCESSES
P1 P2
p1 = spawn(fn ->
end)
# > #PID<0.86.0>
Process.alive?(p1)
# > false
p2 = self()
# > #PID<0.95.0>
Process.alive?(p2)
# > true
PROCESSES COMMUNICATION
P1 P2
p1 = self()
# > #PID<0.81.0>
msg = "Ping"
# > "Ping"
p2 = spawn(fn ->
end)
# > Ping
# > #PID<0.101.0>
receive do
msg -> IO.puts(msg)
end
# > Pong
# > :ok
IO.puts(msg)
send(p1, "Pong")
PROCESSES COMMUNICATION
P1 P2
p1 = self()
# > #PID<0.81.0>
msg = "Ping"
# > "Ping"
p2 = spawn(fn ->
end)
# > Ping
# > #PID<0.101.0>
receive do
msg -> IO.puts(msg)
end
# > Pong
# > :ok
IO.puts(msg)
send(p1, "Pong")
PROCESSES COMMUNICATION
P1 P2
p1 = self()
# > #PID<0.81.0>
msg = "Ping"
# > "Ping"
p2 = spawn(fn ->
end)
# > Ping
# > #PID<0.101.0>
receive do
msg -> IO.puts(msg)
end
# > Pong
# > :ok
MESSAGE
IO.puts(msg)
send(p1, "Pong")
PROCESSES LINKING
P1 P2
spawn_link(fn ->
end)
P1 P2p2 = spawn(fn ->
end)
# > #PID<0.113.0>
Process.link(p2)
# > true
raise "Dad, I'm dead"
# > ** (EXIT from #PID<0.81.0>) an
exception was raised:
# > ** (RuntimeError) Dad, I'm
dead
:timer.sleep(1000)
raise "Mom, I'm dead”
# > ** (EXIT from #PID<0.81.0>) an
exception was raised:
# > ** (RuntimeError) Mom, I'm
dead
# > :ok
PROCESSES LINKING
P1 P2
spawn_link(fn ->
end)
P1 P2p2 = spawn(fn ->
end)
# > #PID<0.113.0>
Process.link(p2)
# > true
raise "Dad, I'm dead"
# > ** (EXIT from #PID<0.81.0>) an
exception was raised:
# > ** (RuntimeError) Dad, I'm
dead
:timer.sleep(1000)
raise "Mom, I'm dead”
# > ** (EXIT from #PID<0.81.0>) an
exception was raised:
# > ** (RuntimeError) Mom, I'm
dead
# > :ok
PROCESSES LINKING
P1 P2
spawn_link(fn ->
end)
P1 P2p2 = spawn(fn ->
end)
# > #PID<0.113.0>
Process.link(p2)
# > true
raise "Dad, I'm dead"
# > ** (EXIT from #PID<0.81.0>) an
exception was raised:
# > ** (RuntimeError) Dad, I'm
dead
:timer.sleep(1000)
raise "Mom, I'm dead”
# > ** (EXIT from #PID<0.81.0>) an
exception was raised:
# > ** (RuntimeError) Mom, I'm
dead
# > :ok
L
L
L
L
|> Application discovery
|> Failure detection
|> Processes management
|> Hot code swapping
|> Server structure
OTP
ERLANG MNESIA
L
L
L
L
OTP+ + =
A
A
L
L
L
L
|> Application discovery
|> Failure detection
|> Processes management
|> Hot code swapping
|> Server structure
OTP
ERLANG MNESIA
L
L
L
L
OTP+ + =
A
A
P
P P
|> Task (performs async work)
|> Agent (holds state)
|> GenServer (general purpose client-server scaffold)
|> Supervisor
OTHER CONCURRENT ABSTRACTIONS
TASK
defmodule Tea.Master do
def brew do
IO.puts "Brewing tea..."
:timer.sleep(1000)
IO.puts "Tea is ready!"
"a cup of tea"
end
end
IO.puts "[Tea Master] Please sit down and take a rest"
IO.puts "[Tea Master] I need some time to brew the tea"
worker = Task.async(fn -> Tea.Master.brew() end)
IO.puts "[Guest] I have to clean my mind"
result = Task.await(worker)
IO.puts "The result is #{result}"
# > [Tea Master] Please sit down and take a rest
# > [Tea Master] I need some time to brew the tea
# > [Guest] I have to clean my mind
# > Brewing tea...
# > Tea is ready!
# > The result is a cup of tea
AGENT
{ :ok, wisdom } = Agent.start(fn -> "Elixir" end)
# > {:ok, #PID<0.83.0>}
Agent.get(wisdom, &(&1))
# > "Elixir"
Agent.update(wisdom, &(&1 <> "is awesome"))
# > :ok
Agent.get(wisdom, &(&1))
# > "Elixiris awesome"
# > nil
Agent.start(fn -> [] end, name: Fridge)
# > {:ok, #PID<0.89.0>}
Agent.update(Fridge, &(["milk" | &1]))
# > :ok
Agent.get(Fridge, &(&1))
# > ["milk"]
Agent.update(Fridge, &(["butter" | &1]))
# > :ok
Agent.get(Fridge, &(&1))
# > ["butter", "milk"]
GENSERVER
defmodule Stack do
use GenServer
# Client API
def start_link do
GenServer.start_link(__MODULE__, :ok, [])
end
def push(pid, item) do
GenServer.cast(pid, {:push, item})
end
def pop(pid) do
GenServer.call(pid, :pop)
end
# Server API
def handle_call(:pop, _from, state) do
[item | next_state] = state
{:reply, item, next_state}
end
def handle_cast({:push, item}, state) do
next_state = [item | state]
{:noreply, next_state}
end
end
{:ok, stack} = Stack.start_link
# > {:ok, #PID<0.117.0>}
Stack.push(stack, "milk")
# > :ok
Stack.push(stack, "butter")
# > :ok
Stack.pop(stack)
# > "butter"
Stack.pop(stack)
# > "milk"
SUPERVISOR
defmodule Mathex do
use Application
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
import Supervisor.Spec
# Define workers and child supervisors to be supervised
children = [
supervisor(Mathex.Repo, []),
supervisor(Mathex.Endpoint, []),
# Start your own worker by calling: Mathex.Worker.start_link(arg1, arg2, arg3)
# worker(Mathex.Worker, [arg1, arg2, arg3]),
]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Mathex.Supervisor]
Supervisor.start_link(children, opts)
end
# Tell Phoenix to update the endpoint configuration
# whenever the application is updated.
def config_change(changed, _new, removed) do
Mathex.Endpoint.config_change(changed, removed)
:ok
end
end
APPLICATION
APPLICATION
P1
P1 P1
UMBRELLA PROJECTS
☂
APPLICATION
P1
P1 P1
APPLICATION
P1
P1 P1
DOCUMENTATION
|>First-class citizen
|>h helper
|>@docmodule
|>@doc
|>exdoc
|>doctets
|>README Driven Development
defmodule YAC do
@moduledoc """
Yet Another Calculator
"""
@doc """
Sum two numbers
## Examples
iex> sum(1, 2)
3
"""
def sum(a, b) do
a + b
end
end
|> https://learnxinyminutes.com/docs/elixir
|> http://elixir-lang.org
|> http://blog.plataformatec.com.br/2015/06/elixir-in-times-
of-microservices
|> http://howistart.org/posts/elixir/1
|> https://www.youtube.com/watch?v=L0qC97ytMjQ
RESOURCES
?

Elixir

  • 1.
  • 2.
    |> Lack ofmemory -> Accurate manual allocation, but lost pieces of memory + error-prone |> CPU becomes faster -> GC (automatic memory management) |> CPU doesn’t become faster, but with more cores –> Concurrent execution, but shared resources |> Synchronisation -> Hard to code + performance penalty |> Better concurrent model and execution environment -> Erlang VM (perfect for distributed software), but lack of metaprogramming, polymorphism and first-class tooling in ecosystem HISTORY
  • 4.
    ELIXIR IS GREATFOR WRITING HIGHLY PARALLEL, RELIABLE APPLICATIONS. Jose Valim ELIXIR SUPPORTED BY IDIOMS AND CONVENTIONS
  • 5.
    |> Easy Concurrent(Actor Model) |> Fault Tolerant |> Functional Paradigm |> Extensible & DSL |> Metaprogramming |> Leverage all Erlang legacy ELIXIR
  • 6.
    Immutable data isknown data |> No way to concurrence data mess |> Because handle state gracefully IMMUTABILITY Performance Implications |> Copying Data |> Garbage Collection (per process)
  • 7.
  • 8.
    |> Integers Unlimited |>Floating-Point Numbers IEEE 754 |> Only Integer Ranges VALUE TYPES 1 # > 1 1_000_000_000 # > 1000000000 3.14 # > 3.14 2.99792458e8 # > 299792458.0 :atom # > :atom :"molecula" # > :molecula 1..10 # > 1..10 true == :true # > true false == :false # > true nil # > nil ~r/[aeiou]/ # > ~r/[aeiou]/
  • 9.
    PATTERN MATCHING list =[1, 2, 3] # > [1, 2, 3] [_, teas] = [3, ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]] # > [3, ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]] teas # > ["Puer", "Da Hong Pao", "Longjing", "Biluochun"] [tea, tea] = [1, 2]
  • 10.
    PATTERN MATCHING list =[1, 2, 3] # > [1, 2, 3] [_, teas] = [3, ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]] # > [3, ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]] teas # > ["Puer", "Da Hong Pao", "Longjing", "Biluochun"] [tea, tea] = [1, 2] # > ** (MatchError) no match of right hand side value: [1, 2]
  • 11.
    PATTERN MATCHING list =[1, 2, 3] # > [1, 2, 3] [_, teas] = [3, ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]] # > [3, ["Puer", "Da Hong Pao", "Longjing", "Biluochun"]] teas # > ["Puer", "Da Hong Pao", "Longjing", "Biluochun"] [tea, tea] = [1, 2] tea = "Puer" # > "Puer" "Puer" # > "Puer" tea = "Da Hong Pao" # > "Da Hong Pao" "Da Hong Pao" # > "Da Hong Pao" ^tea = "Longjing" # > ** (MatchError) no match of right hand side value: "Longjing" # > ** (MatchError) no match of right hand side value: [1, 2]
  • 12.
    SYSTEM TYPES self # >#PID<0.80.0> pid = spawn fn -> "Hi" end # > #PID<0.84.0>
  • 13.
    COLLECTION TYPES {1, :atom,"string", {:another, :tuple}} # > {1, :atom, "string", {:another, :tuple}} [1, :atom, "string", [:another, :list]] # > [1, :atom, "string", [:another, :list]] [first_name: "Menya", last_name: "Musashi"] # > [first_name: "Menya", last_name: "Musashi"] f = fn args -> "#{Keyword.get(args, :first_name)} #{Keyword.get(args, :last_name)}" end # > #Function<6.52032458/1 in :erl_eval.expr/5> f.(first_name: "Menya", last_name: "Musashi") # # > "Menya Musashi" %{1 => :atom, "string" => %{ another: :map}, "exp" <> "ressions" => 2 + 2} # > %{1 => :atom, "expressions" => 4, "string" => %{another: :map}} %{"key" => "value1", "key" => "value2"} # > %{"key" => "value2"} m = %{some_key: "some_value"} # > %{some_key: "some_value"} m[:some_key] # > "some_value" m.some_key # shortcut for getting value by symbol key # > "some_value" <<1, 2>> # 0000000100000010 – 2 bytes (by default) # > <<1, 2>> bitstring = <<1 :: size(2), 2 :: size(4)>> # 00010010 – 1 byte # > <<18::size(6)>> bit_size(bitstring) # > 6 byte_size(bitstring) # > 1
  • 14.
    STRINGS #{}N T XUTF-8HEREDOC """ HERE DOC """ # > " HEREn DOCn" SIGIL ~c/abc/ # > 'abc' ~r/a.+z/ # > ~r/a.+z/ ~s/"Menya Musashi"/ # > ""Menya Musashi"" ~w<Something for nothing> # > ["Something", "for", "nothing"] ~w[Something for nothing]s # > ["Something", "for", "nothing"] ~w{Something for nothing}a # > [:Something, :for, :nothing] ~w|Something for nothing|c # > ['Something', 'for', 'nothing']
  • 15.
    BINARIES |> Bits List b= <<1, 2, 3>> # 00000001 00000010 00000011 # > <<1, 2, 3>> byte_size(b) # > 3 bit_size(b) # > 24 b = <<1::size(2), 2::size(3)>> # > <<10::size(5)>> byte_size(b) # > 1 bit_size(b) # > 5 <<16245::utf8>> # > "㽵" <<16245::unsigned-integer-size(12)>> # > <<247, 5::size(4)>> <<sign::size(1), exp::size(11), mantissa::size(52)>> = <<3.14159::float>> # > <<64, 9, 33, 249, 240, 27, 134, 110>> (1 + mantissa / :math.pow(2, 52)) * :math.pow(2, exp - 1023) # > 3.14159
  • 16.
    STRINGS AS BINARIES UTF-8BINARY UTF-8 BINARY UTF-8 BINARY UTF-8 BINARY String.length("㽵") # > 1 byte_size("㽵") # > 3 String.codepoints("Nou0308l") # > ["N", "o", "̈", "l"] String.graphemes("Nou0308l") # > ["N", "ö", "l"] to_string(<<97, 98, 99>>) # > "abc" <<97, 98, 99>> # > “abc" is_binary("abc") # > true
  • 17.
    CHARACTER LISTS 'abc' # >'abc' List.to_tuple('abc') # > {97, 98, 99} [97, 98, 99] # > 'abc' ?a # > 97 is_list('abc') # > true |> In practice, char lists are used mostly when interfacing with Erlang, in particular old libraries that do not accept binaries as arguments
  • 18.
    OPERATORS "Menya" <> "Musashi" # > "Menya Musashi" <<97>> <> "bc" # > "abc" [2, "Puer"] ++ [4, "Da Hong Pao"] # > [2, "Puer", 4, "Da Hong Pao"] "Tieguanyin" in ["Tieguanyin", "Maofeng", "Biluochun"] # > true "Gyokuro" in ["Tieguanyin", "Maofeng", "Biluochun"] # > false
  • 19.
    DICTIONARIES kwlist = [tea: "Maofeng", tea: "Tieguanyin", fruit: "Apple" ] # > [tea: "Maofeng", tea: "Tieguanyin", fruit: "Apple"] kwlist[:tea] # > "Maofeng" map = %{ "alpha" => 1, "betta" => 2 } # > %{"alpha" => 1, "betta" => 2} map["alpha"] # > 1 map["gamma"] # > nil map = %{ tea: "Maofeng", tea: "Tieguanyin", fruit: "Apple" } # > %{fruit: "Apple", tea: "Tieguanyin"} map.tea # > "Tieguanyin" map[:tea] # > "Tieguanyin" map[:juice] # > nil defmodule Tea do defstruct name: "Puer", type: :fermented, area: "Yunnan" end %Tea{} # > %Tea{area: "Yunnan", name: "Puer", type: :fermented} tea = %Tea{name: "Tieguanyin", area: "Fujian"} # > %Tea{area: "Fujian", name: "Tieguanyin", type: :fermented} tea.name # > "Tieguanyin" MAP KEYWORD LIST STRUCT
  • 20.
    COMPREHENSIONS MAPGENERATOR FILTER list =[1, 2, 3, 4, 5, 6, 7, 8] # > [1, 2, 3, 4, 5, 6, 7, 8] for x <- list, y <- list, x >= y, rem(x * y, 10) == 0, do: {x, y} # > [{5, 2}, {5, 4}, {6, 5}, {8, 5}] for x <- ~w{b c}, into: %{ "a" => "A" }, do: { x, String.upcase(x) } # > %{"a" => "A", "b" => "B", "c" => "C"}
  • 21.
    ANONYMOUS FUNCTIONS f =fn (parameter list) -> body end f = fn (a, b) -> a + b end # > #Function<12.52032458/2 in :erl_eval.expr/5> f.(1, 2) # > 3 swap = fn [a, b] -> [b, a] end # > #Function<6.52032458/1 in :erl_eval.expr/5> swap.([1, 2]) # > [2, 1] foobar = fn (0, 0) -> "FooBar" (0, _) -> "Foo" (_, 0) -> "Bar" (_, _) -> "Nothing" end # > #Function<12.52032458/2 in :erl_eval.expr/5> foobar.(1, 0) # > "Bar" f = fn -> fn -> "A" end end # > #Function<20.52032458/0 in :erl_eval.expr/5> f.().() # > "A" add = &(&1 + &2) # > &:erlang.+/2 add.(1, 2) # > 3 multiplier = 10 # > 10 Enum.map [1, 2, 3, 4], &(&1 * multiplier) # > [10, 20, 30, 40]
  • 22.
    NAMED FUNCTIONS defmodule Teado def brew(tea, water_temperature 90) def brew(tea, water_temperature) when tea == "Gyokuro" do "Made in Japan. Brewing #{tea} (#{water_temperature} C)..." end def brew(tea, water_temperature) when tea == "Tieguanyin" do "Made in Chaina. Brewing #{tea} (#{water_temperature} C)..." end def brew(tea, water_temperature) do "Brewing #{tea} (#{water_temperature} C)..." end defp think(topic) do "Thinking about #{topic}..." end end Tea.brew("Puer") # > "Brewing Puer (90 C)..." Tea.brew("Gyokuro") # > "Made in Japan. Brewing Gyokuro (90 C)..." Tea.brew("Gyokuro", 60) # > "Made in Japan. Brewing Gyokuro (60 C)..." Tea.brew("Tieguanyin", 80) # > "Made in Chaina. Brewing Tieguanyin (80 C)..." Tea.think("Eternity") # Check spelling # > ** (UndefinedFunctionError) function Tea.think/1 is undefined or private # > Tea.think("Eternity")
  • 23.
    RECURSION defmodule MyList do defsum([]), do: 0 def sum([head | tail]), do: head + sum(tail) end list = [3, 2, 1] # > [3, 2, 1] sum = MyList.sum(list) # > 6 |> Reducing algorithm |> Mapping algorithm
  • 24.
    PIPE OPERATOR defmodule Teado defmodule Master do def brew(tea), do: "Brewing #{tea} with optimal water temperature..." end def find(title), do: title end Tea.Master.brew(Tea.find("Puer")) # > "Brewing Puer with optimal water temperature..." "Puer" |> Tea.find |> Tea.Master.brew # > "Brewing Puer with optimal water temperature..." prepend_awesome = fn x -> "Awesome " <> x end # > #Function<6.52032458/1 in :erl_eval.expr/5> append_try_it = fn x -> x <> ", try it!" end # > #Function<6.52032458/1 in :erl_eval.expr/5> "Elixir" |> prepend_awesome |> append_try_it # Exception # > ** (CompileError) iex:8: undefined function append_try_it/1 # > (elixir) expanding macro: Kernel.|>/2 # > iex:8: (file) "Elixir" |> prepend_awesome.() |> append_try_it.() # > "Awesome Elixir, try it!"
  • 25.
    FROM SOURCE CODETO VIRTUAL MACHINE .EXS .EX ELIXIR COMPILER BYTECODE .BEAM HDD ERLANG VM
  • 26.
    MODULES defmodule Tea.Container do defprepare do IO.puts "Prepare container..." end end defmodule Tea.Pot do @material "yixing clay" # mostly use as a constant, can't be defined inside function definition def prepare do IO.puts "Prepare pot from #{@material}..." end end defmodule Tea.Bowl do def prepare do IO.puts "Prepare bowl..." end end defmodule Tea.Spoon do def prepare do IO.puts "Prepare spoon..." end end defmodule Tea.Utensils do alias Tea.Container alias Tea.{Pot, Bowl} alias Tea.Spoon, as: Scoop def prepare do Container.prepare Pot.prepare Bowl.prepare Scoop.prepare end end defmodule Tea.Master do def prepare(utensils) do utensils.prepare end def choose_tea do IO.puts "Choose tea sort..." end def brew do IO.puts "Brew tea..." end def taste do IO.puts "Thruuup-thruup... so tasty!" end end defmodule Tea.Style do defmacro style(name) do quote do IO.puts "Tea story: #{unquote(name)} style" end end end defmodule Tea.Ceremony do import Tea.Style alias Tea.Master alias Tea.Utensils style :chaineese def run do Master.prepare(Utensils) Master.choose_tea Master.brew Master.taste end end Tea.Ceremony.run # > Prepare container... # > Prepare pot from yixing clay... # > Prepare bowl... # > Prepare spoon... # > Choose tea sort... # > Brew tea... # > Thruuup-thruup... so tasty! # > :ok
  • 27.
    |> Duck Typing |>Type = Primitive implementation + Modules |> @type |> @spec TYPE SUMMARY
  • 28.
    PROTOCOLS defprotocol CaesarCypher do defencrypt(stringlike, shift) def rot13(stringlike) end defimpl CaesarCypher, for: List do def encrypt([], _shift), do: [] def encrypt([head | tail], shift) do [rem((head - 97 + shift), 26) + 97 | encrypt(tail, shift)] end def rot13(list), do: encrypt(list, 13) end defimpl CaesarCypher, for: BitString do def encrypt(string, shift) do to_charlist(string) |> _encrypt(shift) |> to_string end def rot13(string), do: encrypt(string, 13) defp _encrypt([], _shift), do: [] defp _encrypt([head | tail], shift) do [rem((head - 97 + shift), 26) + 97 | _encrypt(tail, shift)] end end CaesarCypher.encrypt('zappa', 1) # > 'abqqb' CaesarCypher.rot13('zappa') # > 'mnccn' CaesarCypher.encrypt("zappa", 1) # > "abqqb" CaesarCypher.rot13("zappa") # > "mnccn"
  • 29.
    |> Elixir islexically scoped |> Basic unit – function body |> Module has it’s own non visible for functions scope |> Comprehensions and with create a separate lexical scope SCOPES
  • 30.
    MACROS |> Language extension |>Never use a macro when you can use a function ELIXIR SOURCE CODE ELIXIR MACRO EXPANSION ELIXIR COMPILER COMPILE TIME BEAM BYTECODE CORE ERLANG
  • 31.
    MACROS defmodule Meta do defmacrodefine_methods(names) do IO.puts("Compile time: #{inspect(names)}") for name <- names do quote do def unquote(name)() do IO.puts("Run time: #{inspect(unquote(name))}") "Hi, from #{unquote(name)} method" end end end end end defmodule M do import Meta define_methods [:alpha, :beta] end # > Compile time: [:alpha, :beta] M.alpha # > Run time: :alpha # > "Hi, from alpha method" M.beta # > Run time: :beta # > "Hi, from beta method"
  • 32.
    MIX Build tool |>Elixir built-in |>Createapplication |>Compile application |>Test application (ExUnit) |>Manage application dependencies |>Environments (MIX_ENV: dev, test, prod) mix new tea_master mix deps.get mix phoenix.new
  • 33.
  • 34.
    PROCESSES |> Lightweight |> Isolated |>Actors P1 MESSAGE MESSAGE THREAD P2
  • 35.
    PROCESS IS NOTONLY ABOUT CONCURRENCY, BUT ALSO ABOUT DISTRIBUTION AND FAULT-TOLERANCE. Jose Valim FAIL FAST, ALLOW SUPERVISOR TO RESTART OR BURY FAILED PROCESS
  • 36.
    PROCESSES P1 P2 p1 =spawn(fn -> end) # > #PID<0.86.0> Process.alive?(p1) # > false p2 = self() # > #PID<0.95.0> Process.alive?(p2) # > true
  • 37.
    PROCESSES P1 P2 p1 =spawn(fn -> end) # > #PID<0.86.0> Process.alive?(p1) # > false p2 = self() # > #PID<0.95.0> Process.alive?(p2) # > true
  • 38.
    PROCESSES COMMUNICATION P1 P2 p1= self() # > #PID<0.81.0> msg = "Ping" # > "Ping" p2 = spawn(fn -> end) # > Ping # > #PID<0.101.0> receive do msg -> IO.puts(msg) end # > Pong # > :ok IO.puts(msg) send(p1, "Pong")
  • 39.
    PROCESSES COMMUNICATION P1 P2 p1= self() # > #PID<0.81.0> msg = "Ping" # > "Ping" p2 = spawn(fn -> end) # > Ping # > #PID<0.101.0> receive do msg -> IO.puts(msg) end # > Pong # > :ok IO.puts(msg) send(p1, "Pong")
  • 40.
    PROCESSES COMMUNICATION P1 P2 p1= self() # > #PID<0.81.0> msg = "Ping" # > "Ping" p2 = spawn(fn -> end) # > Ping # > #PID<0.101.0> receive do msg -> IO.puts(msg) end # > Pong # > :ok MESSAGE IO.puts(msg) send(p1, "Pong")
  • 41.
    PROCESSES LINKING P1 P2 spawn_link(fn-> end) P1 P2p2 = spawn(fn -> end) # > #PID<0.113.0> Process.link(p2) # > true raise "Dad, I'm dead" # > ** (EXIT from #PID<0.81.0>) an exception was raised: # > ** (RuntimeError) Dad, I'm dead :timer.sleep(1000) raise "Mom, I'm dead” # > ** (EXIT from #PID<0.81.0>) an exception was raised: # > ** (RuntimeError) Mom, I'm dead # > :ok
  • 42.
    PROCESSES LINKING P1 P2 spawn_link(fn-> end) P1 P2p2 = spawn(fn -> end) # > #PID<0.113.0> Process.link(p2) # > true raise "Dad, I'm dead" # > ** (EXIT from #PID<0.81.0>) an exception was raised: # > ** (RuntimeError) Dad, I'm dead :timer.sleep(1000) raise "Mom, I'm dead” # > ** (EXIT from #PID<0.81.0>) an exception was raised: # > ** (RuntimeError) Mom, I'm dead # > :ok
  • 43.
    PROCESSES LINKING P1 P2 spawn_link(fn-> end) P1 P2p2 = spawn(fn -> end) # > #PID<0.113.0> Process.link(p2) # > true raise "Dad, I'm dead" # > ** (EXIT from #PID<0.81.0>) an exception was raised: # > ** (RuntimeError) Dad, I'm dead :timer.sleep(1000) raise "Mom, I'm dead” # > ** (EXIT from #PID<0.81.0>) an exception was raised: # > ** (RuntimeError) Mom, I'm dead # > :ok
  • 44.
    L L L L |> Application discovery |>Failure detection |> Processes management |> Hot code swapping |> Server structure OTP ERLANG MNESIA L L L L OTP+ + = A A
  • 45.
    L L L L |> Application discovery |>Failure detection |> Processes management |> Hot code swapping |> Server structure OTP ERLANG MNESIA L L L L OTP+ + = A A P P P
  • 46.
    |> Task (performsasync work) |> Agent (holds state) |> GenServer (general purpose client-server scaffold) |> Supervisor OTHER CONCURRENT ABSTRACTIONS
  • 47.
    TASK defmodule Tea.Master do defbrew do IO.puts "Brewing tea..." :timer.sleep(1000) IO.puts "Tea is ready!" "a cup of tea" end end IO.puts "[Tea Master] Please sit down and take a rest" IO.puts "[Tea Master] I need some time to brew the tea" worker = Task.async(fn -> Tea.Master.brew() end) IO.puts "[Guest] I have to clean my mind" result = Task.await(worker) IO.puts "The result is #{result}" # > [Tea Master] Please sit down and take a rest # > [Tea Master] I need some time to brew the tea # > [Guest] I have to clean my mind # > Brewing tea... # > Tea is ready! # > The result is a cup of tea
  • 48.
    AGENT { :ok, wisdom} = Agent.start(fn -> "Elixir" end) # > {:ok, #PID<0.83.0>} Agent.get(wisdom, &(&1)) # > "Elixir" Agent.update(wisdom, &(&1 <> "is awesome")) # > :ok Agent.get(wisdom, &(&1)) # > "Elixiris awesome" # > nil Agent.start(fn -> [] end, name: Fridge) # > {:ok, #PID<0.89.0>} Agent.update(Fridge, &(["milk" | &1])) # > :ok Agent.get(Fridge, &(&1)) # > ["milk"] Agent.update(Fridge, &(["butter" | &1])) # > :ok Agent.get(Fridge, &(&1)) # > ["butter", "milk"]
  • 49.
    GENSERVER defmodule Stack do useGenServer # Client API def start_link do GenServer.start_link(__MODULE__, :ok, []) end def push(pid, item) do GenServer.cast(pid, {:push, item}) end def pop(pid) do GenServer.call(pid, :pop) end # Server API def handle_call(:pop, _from, state) do [item | next_state] = state {:reply, item, next_state} end def handle_cast({:push, item}, state) do next_state = [item | state] {:noreply, next_state} end end {:ok, stack} = Stack.start_link # > {:ok, #PID<0.117.0>} Stack.push(stack, "milk") # > :ok Stack.push(stack, "butter") # > :ok Stack.pop(stack) # > "butter" Stack.pop(stack) # > "milk"
  • 50.
    SUPERVISOR defmodule Mathex do useApplication # See http://elixir-lang.org/docs/stable/elixir/Application.html # for more information on OTP Applications def start(_type, _args) do import Supervisor.Spec # Define workers and child supervisors to be supervised children = [ supervisor(Mathex.Repo, []), supervisor(Mathex.Endpoint, []), # Start your own worker by calling: Mathex.Worker.start_link(arg1, arg2, arg3) # worker(Mathex.Worker, [arg1, arg2, arg3]), ] # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html # for other strategies and supported options opts = [strategy: :one_for_one, name: Mathex.Supervisor] Supervisor.start_link(children, opts) end # Tell Phoenix to update the endpoint configuration # whenever the application is updated. def config_change(changed, _new, removed) do Mathex.Endpoint.config_change(changed, removed) :ok end end
  • 51.
  • 52.
  • 53.
    DOCUMENTATION |>First-class citizen |>h helper |>@docmodule |>@doc |>exdoc |>doctets |>READMEDriven Development defmodule YAC do @moduledoc """ Yet Another Calculator """ @doc """ Sum two numbers ## Examples iex> sum(1, 2) 3 """ def sum(a, b) do a + b end end
  • 54.
    |> https://learnxinyminutes.com/docs/elixir |> http://elixir-lang.org |>http://blog.plataformatec.com.br/2015/06/elixir-in-times- of-microservices |> http://howistart.org/posts/elixir/1 |> https://www.youtube.com/watch?v=L0qC97ytMjQ RESOURCES
  • 55.