Skip to content

pierrelegall/erlang-elixir-presentation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Erlang/OTP and Elixir

Why this presentation ?

I like Ruby but…

  • concurrency in Ruby is not easy
  • microsevices with Ruby is as complex as with Node or Java

The microservice pattern is cool but…

*** Simplicity

./images/monolith-vs-microservices-hapiness.png

Productivity

./images/monolith-vs-microservices-productivity.png

I don’t do nor Erlang/OTP neither Elixir yet but…

Hype from the Ruby community

A lot of people from the Ruby community talk and migrate to Elixir 🤔

Slant stats

Elixir on Slant

Erlang: an Ericson story

The problem

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!

A solution: Erlang/OTP

An implementation of the actor model

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

Lightweight processes

./images/lightweight-processes.png

See more explanations here.

Fault tolerant

Concept:

  • processes are allowed to fail
  • a local error can be manage by another process

Hot code reloading

Is it proven?

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
  • Pinterest
  • Many game server (from EA Games, Activision, Blizzard…)

And software like:

  • Databases: Riak, CouchDB
  • Message broker: RabbitMQ
  • XMPP server: ejabber2

Why so misknown?

Thinking processes: a diferent way to think

  • no shared memory 😱
  • no thread
  • no lock
  • no mutex
  • no …
  • you should deal with processes and message passing

Functinal programming: who does not use OOP?

  • only functions and values (no classes, no object, no method) 😱
  • forced immutability 😱
  • tail recursion only, no syntactic loops available 😱

Non-classic syntax

-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?

Entr’acte

Some vacabulary

  • 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

Warning!

  • the BEAN is often named the Erlang VM or the EVM
  • when we talk about Erlang, we often think about the BEAM capabilities

Diving into Erlang/OTP (with Elixir)

The BEAM

Process management

./images/beam-processes.jpg

Isolation

./images/message-passing.jpg

  • no shared memory
  • only message passing
  • one GC by process

One scheduler by core

./images/beam-one-scheduler-by-core.png

A « side effect »

Scalability

At least n x (0.75 x nb_of_cpu), without changing the source code.

./images/beam-concurrency.jpg

Note you can clusterize multiple BEAM VMs (can be automized).

Elixir

Big lines

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.

At the beginning

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.

How it looks like

Basic types

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" }

Control structures

if/unless
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"
case
result = {:ok, "Hello World"}

case result do
  {:ok, result} -> result
  {:error} -> "Uh oh!"
  _ -> "Catch all"
end
#=> "Hello World"
cond
cond do
  7 + 1 == 0 -> "Incorrect"
  8 + 3 == 11 -> "Correct"
  true -> "Catch all"
end
#=> "Correct"

Modules and functions

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

Struct

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

Pattern matching (on value)

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

Pipe operator

If we want to:

  • Take a collection
  • remove zeros and negative numbers
  • then compute there square

How should we write it ?

Version 1
Enum.map(
  Enum.filter(
    [1, 2, -3, 4, -5],
    fn (x) -> x > 0 end
  ),
fn (x) -> x * x end)
Version 2
numbers = [1, 2, -3, 4, -5]
numbers = Enum.filter(numbers, fn (x) -> x > 0 end)
numbers = Enum.map(numbers, fn (x) -> x * x end)
Version 3

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!

Protocols

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

Documentation and testing

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

And more

  • anonymous functions
  • comprehensions
  • macros
  • guards

Tooling

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)

OTP

Run functions asynchronously with Task.async

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)

Creating processes with Kernel.spawn

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

Standardise processes interface with GenServer

See Elixir GenServer basics.

Supervise with Supervisor

./images/supervisor-tree.png

./images/supervisor-restartson-for-one.png

The ecosystem

hex.pm

An Elixir package repository.

Phoenix framework

A back-end framework on the EVM

A consistent back-end stack

./images/classical-stack-vs-erlang-stack.png

IoT capable

The Nerves project

Conclusion

Monolith vs microservices with Elixir

Manage it as a monolith, run it as microservices.

Some people call it nanoservices or microlith.

Pros and cons

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

Sources

About

WIP: A presentation of Erlang/OTP and Elixir

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published