Elixir: a practical introduction

Geovane Fedrecheski - geovane@lsi.usp.br

School of Engineering

University of São Paulo

Contents

  • Functional Programming
  • Elixir
  • Hands-on

Functionaλ Programming

f(g(x), h(...))

Steve Vinoski, IEEE Computing Society 2009

"... ironically, the resurgence of interest in FP languages owes a lot to Java’s popularity."

"Another reason [...] is the move toward multicore architectures."

"... developers look for the opportunity to
get more done with less."

Welcome to the Functional Web

Functional Programming Languages

Lisp

Elm

Elixir

José Valim

Core committer

Unsatisfied with thread-based concurrency

~2010

Unsatisfied with thread-based concurrency

Repetitive strain

injury

by Bruce A. Tate

So what's interesting about

this Erlang thing?

What even an "Erlang" is?

A bit of History

Erlang

context

Created in the Telecommunication industry

...

~1980

Erlang

requirements

  • Concurrency
  • Soft real-time
  • Distribution
  • Hardware Interaction
  • Large Software Systems
  • Complex Functionality
  • Continuous Operation
  • Quality Requirements
  • Fault Tolerance

Programming language

Virtual Machine - BEAM

Framework - OTP

-module(fact).
-export([fac/1]).

fac(0) -> 1;
fac(N) -> N * fac(N-1).

Cases

The VM

Concurreny

Fault tolerance

Data structures

Consistent libs

Modern tooling

I loved everything that Erlang had

but I hated everything that it didn't had

- José Valim

Elixir

~2011

Elixir

Goals

  • Extensibility

  • Productivity

  • Compatibility

Elixir

Erlang

Features

Pattern Matching

> {method, path} = {:get, "temp"}
{:get, "temp"}
> method
:get
> path
"temp"
b = :binary.encode_unsigned(258)
# b has value <<1, 2>>

<<x, 2>> = b
# x has value 1

i = :binary.decode_unsigned(<<x, 2>>)
# i has value 258
request = {:get, "temp"}



case request do
  {:post, path, payload} ->
    send_resp 200
  {:get, path} ->
    send_resp 200, "{'value':17.5, 'unit': 'celsius'}"
end
defmodule Math do
  def fib(0), do: 0
  def fib(1), do: 1
  def fib(n), do: fib(n-1) + fib(n-2)
end

Math.fib(8) # => 21

Anonymous Functions

> add = fn(a, b) -> a + b end
#Function<12.71889879/2 in :erl_eval.expr/5>
> add.(1, 2)
3
> Enum.map([17.9, 20.2, 18.4], fn(x) -> x * 2 end)
[35.8, 40.4, 36.8]

Can be passed as parameters

Processes

spawn

A lightweight thread of execution

(The key Erlang/Elixir abstraction)

How is this different from threads?

  • No shared memory

  • VM Processes

Do not share memory

Communicate via

Message Passing

message

VM Processes

Excuse me, have you ever heard of (VM) processes?

Erlang VM

Scheduler

Sched.

Sched.

Sched.

OS Processes

OS Threads

VM Processes

OS

pid = spawn(fn ->
  :timer.sleep(3000)
  IO.puts "hi"
end)

fn ..

spawn

self()

sleep
puts

A lightweight thread of execution

Messages

pid = spawn(...)
send(pid, "hello")
receive do
  msg ->
    IO.puts msg
end

fn ..

spawn

self()

receive
send
Node.spawn(
  :"app@computer-2",
  fn -> Hello.world end)
send
receive
app@computer-1
app@computer-2

Distributed

Same syntax

We have:

  • Isolated Processes

  • Message passing

  • Distribution

We want:

  • Fault Tolerance

dangling processes!

spawn
spawn

call the supervisor

supervisor

worker

worker

worker

worker

Supervision Tree

import Supervisor.Spec

children = [
  worker(Cache, []),
  supervisor(DatabaseSupervisor, []),
  supervisor(TCPSupervisor, [4040])
]

Supervisor.start_link(children, strategy: :one_for_one)

Applications

your app

http
server

logger

app@computer-1

Distributed Applications

app@computer-2

Fault Tolerance

Open Telecom Platform

  • Supervisor

  • Application

  • GenServer

  • GenEvent

OOP

Objects

Elixir

Processes

Production-ready?

CoAP

  • Constrained Application Protocol

  • kind of a "smaller HTTP, for IoT"

request

response

CoAP - Codes

(binary header)

0.01 (GET) (Request)

0.02 (POST) (Request)

2.05 (Content) (Response)

4.04 (Not Found) (Response)

000.00001

000.00010

010.00101

100.00100

CoAP

parser

1

0

0

0.01

123

01000000 00000001 0000000001111011 11111111

255

64 1 0 123 255

(empty)

(empty)

(empty)

%Message(type: :con,
         code: :get,
         msg_id: 123,
         options: [])
int coap_parseHeader(coap_header_t *hdr, 
                     const uint8_t *buf,
                     size_t buflen)
{
    if (buflen < 4)
        return COAP_ERR_HEADER_TOO_SHORT;
    hdr->ver = (buf[0] & 0xC0) >> 6;
    if (hdr->ver != 1)
        return COAP_ERR_VERSION_NOT_1;
    hdr->t = (buf[0] & 0x30) >> 4;
    hdr->tkl = buf[0] & 0x0F;
    hdr->code = buf[1];
    hdr->id[0] = buf[2];
    hdr->id[1] = buf[3];
    return 0;
}

https://github.com/1248/microcoap/blob/master/coap.c

C

def decode(<<1::2, type::2, token_len::4,
             code_class::3, code_detail::5,
             msg_id::16,
             token::binary-size(token_len),
             rest::binary>>) do
def decode(<<1::2, type::2, token_len::4,
             code_class::3, code_detail::5,
             msg_id::16,
             token::binary-size(token_len),
             rest::binary>>) do
  code = decode_code(code_class, code_detail)
  token = decode_token(token, token_len)
  {options, payload} =
    decode_options_and_payload(rest)

  %Message{
    version: 1,
    type: Registry.from(:types, type),
    code: code,
    msg_id: msg_id, token: token,
    options: options, payload: payload
  }
end
int coap_parseHeader(coap_header_t *hdr, 
                     const uint8_t *buf,
                     size_t buflen)
{
    if (buflen < 4)
        return COAP_ERR_HEADER_TOO_SHORT;
    hdr->ver = (buf[0] & 0xC0) >> 6;
    if (hdr->ver != 1)
        return COAP_ERR_VERSION_NOT_1;
    hdr->t = (buf[0] & 0x30) >> 4;
    hdr->tkl = buf[0] & 0x0F;
    hdr->code = buf[1];
    hdr->id[0] = buf[2];
    hdr->id[1] = buf[3];
    return 0;
}

Hands on!

  • Can read & parse its own Service Description
  • Can Register itself in the Broker
  • Has a REST API
  • Enforces Access Control
  • Has unit tests
  • Can keep state

Goal:

Build a

Temperature Sensor

service that:

#1

Create a new project

$ mix new temperature_sensor --sup

$ cd temperature_sensor

$ mix test

$ iex -S mix

#2

def read_service_description do
  Path.join("#{:code.priv_dir(:temperature_sensor)}", 
            "service_description.jsonld")
  |> File.read!()
end
$ mix test
$ iex -S mix

#3

# mix.exs

defp deps do
  [{:poison, "~> 3.0"}]
end
def read_service_description do
  Path.join("#{:code.priv_dir(:temperature_sensor)}", 
            "service_description.jsonld")
  |> File.read!()
  |> Poison.decode!()
end
$ mix deps.get
$ mix test

#4

def register(sd) do
  Task.start_link(fn ->
    BrokerHTTPClient.register(sd)
  end)
end
# mix.exs

defp deps do
  [
    {:poison, "~> 3.0"},
    {:broker_http_client, 
      git: "git@git.febrace.org.br:swarm-unit/broker-http-client-ex.git"}
  ]
end
$ mix deps.get
$ mix test

#5

Plug.Adapters.Cowboy.child_spec(:http, 
                                TemperatureSensor.Router, 
                                [], [port: sd_port])
# mix.exs

defp deps do
  [
    {:cowboy, "~> 1.0.0"},
    {:plug, "~> 1.0"},
    {:poison, "~> 3.0"},
    {:broker_http_client,
     git: "git@git.febrace.org.br:swarm-unit/broker-http-client-ex.git"}
  ]
end
$ mix deps.get
$ iex -S mix

#6

defmodule RouterTest
$ mix test

#7

match _ do
  Logger.warn "Invalid request at #{conn.request_path}"
  send_resp(conn, 404, "EEEPA")
end
$ mix test
$ iex -S mix

#8

plug BrokerHTTPClient, :authorize
$ iex -S mix
$ curl \
  -H "x-m2m-origin: alice" 
  http://localhost:8001/temperature-sensor/value
  • Can read & parse its own Service Description
  • Can Register itself in the Broker
  • Has a REST API
  • Enforces Access Control
  • Has unit tests
  • Can keep state

Goal:

Build a

Temperature Sensor

service that:

Obrigado

Gracias

Merci

Thanks

Appendix

:observer.start

(slide intentionally left blank)

(this is the appendix!)

Made with Slides.com