- concurrency in Ruby is not easy
- microsevices with Ruby is as complex as with Node or Java
*** Simplicity
A lot of people from the Ruby community talk and migrate to Elixir 🤔
In the 80s, Erisson needed a tool/language wich can do:
- granularity of concurrency (one asyncronous telephony process -> one process)
- error recovery capabilities
- hot code reloading (updates without affecting the users)
After experiences on more than 20 languages, they conclude that tool does not already exists!
An actor is an entity that, in response to a message it receives, can:
- send messages to other actors
- create new actors
- change its state
Erlang processes are actors.
It allows:
- isolation: if one actor crashes, it should only affect that actor
- distribution: actors should be able to communicate over a network as if they were on the same machine
- fairness: no actor is allowed to starve any other actor
See more explanations here.
Concept:
- processes are allowed to fail
- a local error can be manage by another process
The Erlang plateform is used by services like:
- Ericsson: power ~50% of phone telecom in EU
- Whatsapp: 5.000.000 connections by node with only 10 devs/devops (when sold to FB)
- Facebook (chat)
- Discord
- Many game server (from EA Games, Activision, Blizzard…)
And software like:
- Databases: Riak, CouchDB
- Message broker: RabbitMQ
- XMPP server: ejabber2
- no shared memory 😱
- no thread
- no lock
- no mutex
- no …
- you should deal with processes and message passing
- only functions and values (no classes, no object, no method) 😱
- forced immutability 😱
- tail recursion only, no syntactic loops available 😱
-module(count_to_ten).
-export([count_to_ten/0]).
count_to_ten() -> do_count(0).
do_count(10) -> 10;
do_count(N) -> do_count(N + 1).
Who said obscur syntax?
- The BEAM: the VM design by Ericson to build concurrent and fault tolerant software
- Erlang: the language design by Ericson to run on the BEAM
- Elixir: a new language wich run on the BEAM, it’s an alternative to the Erlang language
- the BEAN is often named the Erlang VM or the EVM
- when we talk about Erlang, we often think about the BEAM capabilities
- no shared memory
- only message passing
- one GC by process
At least n x (0.75 x nb_of_cpu)
, without changing the source code.
Note you can clusterize multiple BEAM VMs (can be automized).
Elixir is:
- dynamicly and strongly typed
- functional
- compiled (but intepretable too)
What Elixir brings to the table is a complete different surface syntax, inspired by Ruby. What you might called an non-scary syntax, and a load of extra goodies. – Joe Armstrong
Rubyish syntax, but it’s really Erlang.
José Valim made Elixir because:
- Ruby (MRI) threads are not real threads
- Ruby on Rails thread safety is hard to keep
- he heards about what we can do with Erlang
Development:
- start in 2010
- version 1.0 released in 2014.
integer = 42
float = 0.42
boolean = true, false
atom = :atom
string = "elixir"
tuple = {1, 2, true}
list = [1, 2, true]
map = %{ a: 1, b: 2, c: true }
struct = %User{ first_name: "John", last_name: "Doe" }
if 1 == 2 do
"Truthy"
else
"Falsy"
end
#=> "Falsy"
if "a string value" do
"Truthy"
end
#=> "Truthy"
unless is_integer("hello") do
"Not an integer"
end
#=> "Not an integer"
result = {:ok, "Hello World"}
case result do
{:ok, result} -> result
{:error} -> "Uh oh!"
_ -> "Catch all"
end
#=> "Hello World"
cond do
7 + 1 == 0 -> "Incorrect"
8 + 3 == 11 -> "Correct"
true -> "Catch all"
end
#=> "Correct"
defmodule CountToTen do
def count_to_ten do
do_count(0)
end
defp do_count(10) do
10
end
defp do_count(n) do
do_count(n + 1)
end
end
How to define it?
defmodule Banana do
defstruct [:size, :color]
@power_multiplicator 42
def power(banana) do
banana.size * @power_multiplicator
end
end
How to use it?
a_banana = %Banana{ size: 5, color: :blue }
Banana.power(a_banana)
#=> 210
On assignment:
# Lists
list = [1, 2, 3]
[1, 2, 3] = list #=> [1, 2, 3]
[] = list #=> (MatchError) no match of right hand side value: [1, 2, 3]
[1 | tail] = list #=> [1, 2, 3]
tail #=> [2, 3]
[2 | _] = list #=> (MatchError) no match of right hand side value: [1, 2, 3]
# Tuples
{:ok, value} = {:ok, "Successful!"} #=> {:ok, "Successful!"}
value #=> "Successful!"
{:ok, value} = {:error} #=> (MatchError) no match of right hand side value: {:error}
In function:
defmodule Length do
def of([]), do: 0
def of([_ | tail]), do: 1 + of(tail)
end
If we want to:
- Take a collection
- remove zeros and negative numbers
- then compute there square
How should we write it ?
Enum.map(
Enum.filter(
[1, 2, -3, 4, -5],
fn (x) -> x > 0 end
),
fn (x) -> x * x end)
numbers = [1, 2, -3, 4, -5]
numbers = Enum.filter(numbers, fn (x) -> x > 0 end)
numbers = Enum.map(numbers, fn (x) -> x * x end)
And now, with the pipe operator 😍
[1, 2, -3, 4, -5]
|> Enum.filter(fn (x) -> x > 0 end)
|> Enum.map(fn (x) -> x * x end)
Allow method chaining style in functional languages!
Came from the Clojure protocols.
How to add a new protocol:
defprotocol Sound do
def of(thing)
end
How to add implementations:
defimpl Sound, for: Dog do
def of(string), do: "Woof"
end
defimpl Sound, for: Cat do
def of(map), do: "Meow"
end
defimpl Sound, for: Truck do
def of(tuple), do: "VROUM VROUM"
end
or
defmodule Cat do
defimpl Sound do
def of(_) do
"Meow"
end
end
end
defmodule Counter do
@moduledoc """
Documentation for Demo module.
"""
@doc """
Count to ten.
## Examples
iex> Demo.count
100_000_000
"""
def count do
do_count(0)
end
defp do_count(100_000_000) do
100_000_000
end
defp do_count(n) do
do_count(n + 1)
end
end
defmodule CounterTest do
use ExUnit.Case
doctest Counter
test "#count/0" do
assert Demo.count() == 100_000_000
end
end
- anonymous functions
- comprehensions
- macros
- guards
Mix
centralize your Elixir needs:
- compiles your project
- manages dependencies
- runs customs tasks (and is pluggable)
- lints your code
- run a REPL in your application (
iex -S mix
) - manage sub-application (thanks to Umbrella)
Task.async(fn -> Demo.count end) |> Task.await
vs.
for _ <- 1..4 do
Task.async(fn -> Demo.count end)
end |> Enum.map(&Task.await/1)
defmodule Player do
def play do
receive do
{:ping, player} -> send(player, {:pong, self()})
end
end
end
defmodule PingPongTest do
use ExUnit.Case
test "let's play ping pong!" do
player = spawn(Player, :play, []) # create a process
send(player, {:ping, self()}) # send a message to the process
assert_receive {:pong, player}
end
end
An Elixir package repository.
A back-end framework on the EVM
The Nerves project
Manage it as a monolith, run it as microservices.
Some people call it nanoservices or microlith.
Pros:
- Erlang/OTP scale since the last 80s thanks to an « elastic » systems
- Erlang/OTP has a complete « microservices stack » in a very consistent ecosystem
- Erlang/OTP does not force you to create N source code repositories for technical reason
- Erlang/OTP fault tolerance + hot reloading = nice uptime (99.999%)
- Erlang/OTP systems are monitorable (?) = easier to hotfix
- Erlang/OTP is today even better with Elixir 🎉
Cons:
- An Erlang process is not fast as a Java thread
- Elixir is yet another language
- Erlang/OTP developers have to think about processes
- Microservices resources
- Erlang history
- Erlang the movie
- Actor model wikipedia page
- Hitchhiker’s Tour of the BEAM
- BEAM VM Wisdoms
- How we program multicores – Joe Armstrong
- Elixir/Erlang OTP in Microservice Architecture – Thomas Newton
- Elixir: The only Sane Choice in an Insane World – Brian Cardarella
- A Practical Guide to Elixir Protocols – Kevin Rockwood