2 oz. elixir

@fxguillemette

Why elixir?

  • functional programming
  • transparent concurrency
  • metaprogramming
  • dynamic and structural typing
  • polymorphism
  • comprehensions
  • tail-call optimization
  • immuability
  • pattern matching
  • distributed and asynchronous processes
  • robust platform (OTP)
  • process supervisors
  • etc…

Pattern matching

Fibonacci (1)

          
defmodule Fibonacci do
  def of(0), do: 0
  def of(1), do: 1
  def of(n), do: of(n-2) + of(n-1)
end
          
        

Fibonacci (2)

          
defmodule Fibonacci do
  def of(n) when n < 2, do: n
  def of(n), do: of(n-2) + of(n-1)
end
          
        

Fibonacci (3)

          
defmodule Fibonacci do
  def of(n), do: fib_of(n, 0, 1)

  defp fib_of(0, curr_fib, _), do: curr_fib
  defp fib_of(limit, curr_fib, next_fib) do 
    fib_of(limit-1, next_fib, curr_fib + next_fib)
  end
end
          
        

Factorial

          
defmodule Factorial do
  def of(n), do: facto_of(n, 1)

  defp facto_of(0, acc), do: acc
  defp facto_of(1, acc), do: acc
  defp facto_of(n, acc), do: facto_of(n-1, acc*n)
end
          
        

Summing a list

          
defmodule Sum do
  def of(list), do: sum_of(list, 0)

  defp sum_of([], acc), do: acc
  defp sum_of([x|xs], acc), do: sum_of(xs, x+acc)
end
          
        

Anonymous functions

ES6

          
const squared = (x) => Math.pow(x, 2);
squared(4); //=> 16
          
        

elixir

          
squared = fn x -> :math.pow(x, 2) end
squared.(4) #=> 16.0
          
        

ES6 + underscore

          
const squared = _.partial(Math.pow, _, 2);
squared(4); //=> 16
          
        

elixir

          
squared = &:math.pow(&1, 2)
squared.(4) #=> 16.0
          
        

ES6

          
const pow = Math.pow;
pow(4, 2); //=> 16
          
        

elixir

          
pow = &:math.pow/2
pow.(4, 2) #=> 16.0
          
        

pipeline

Happy numbers

$$ 19 \rightarrow 1^2 + 9^2 = 82 \\ 82 \rightarrow 8^2 + 2^2 = 68 \\ 68 \rightarrow 6^2 + 8^2 = 100 \\ 100 \rightarrow 1^2 + 0^2 + 0^2 = 1 \\ $$

          
defmodule Happy do
  def is_happy(n) do
    do_is_happy(n, [])
  end

  defp do_is_happy(1, _), do: true
  defp do_is_happy(n, past), do: ???
          
        
          
defp do_is_happy(n, past) do
  if n in past do
    false
  else
    n
    |> digits
    |> Enum.reduce(0, &sum_of_squares/2)
    |> do_is_happy([n|past])
  end
end
          
        
          
defp digits(num) do
  num
  |> to_string
  |> String.split("", trim: true)
  |> Enum.map(&Integer.parse/1)
end
          
        
          
defp sum_of_squares({digit, _}, acc) do
  (:math.pow(digit, 2) |> trunc) + acc
end
          
        

comprehensions

          
iex> for x <- 1..5, y <- 1..5, x < y, do: {x,y}

[{1, 2}, {1, 3}, {1, 4}, {1, 5}, {2, 3},
 {2, 4}, {2, 5}, {3, 4}, {3, 5}, {4, 5}]
          
        

Prime numbers

          
defmodule PrimeNumbers do
  def upto(n) do
    sieve = :math.sqrt(n) |> trunc

    non_primes = (for i <- 2..sieve do
      multiples_of(i, i*i, n, [])
    end) |> List.flatten

    for i <- 1..n, !(i in non_primes), do: i
  end
          
        
          
defp multiples_of(_, curr, limit, acc)
  when curr > limit, do: acc

defp multiples_of(multiple, curr, limit, acc) do
  multiples_of(multiple, curr+multiple, limit, [curr|acc])
end
          
        

Just one more…

Distributed programming

Distributed programming

  • Actors
  • GenServers
  • GenEvents
  • Tasks
  • Agents
  • Supervisors
  • Nodes

Actors

“An actor is an independent process that shares nothing with any other process.

You can spawn new processes, send them messages, and receive messages back.”

Programming Elixir

A primitive actor

          
defmodule Greeter do
  def start do
    receive do
      {sender, {:greet, name}} ->
        send sender, {:ok, "Hello, #{name}!"}
        start
    end
  end
end
          
        

calling an actor

          
pid = spawn(Greeter, :start, [])
send pid, {self, {:greet, "World"}}

receive do
  {:ok, greetings} ->
    IO.puts greetings #=> "Hello, World!"
  after 5000 ->
    IO.puts "timed out"
end
          
        

GenServer

“A GenServer is a process that can be used to keep state, execute code asynchronously and so on.”

Elixir documentation

“The advantage of using a generic server process is that it will include functionality for tracing and error reporting and will also fit into a supervision tree.”

Elixir documentation

a genserver

          
defmodule Greeter do
  use GenServer

  def init(greeting) do
    {:ok, %{greeting: greeting}}
  end
  def handle_call({:greet, name}, _sender, state) do
    {:reply, "#{state.greeting}, #{name}!", state}
  end
end
          
        

calling a genserver

          
{:ok, pid} = GenServer.start_link(Greeter, "Hello")
GenServer.call(pid, {:greet, "World"}) #=> "Hello, World!"
          
        

client API

          
defmodule Greeter do
  use GenServer
  # client api...
  def start_link(greeting) do
    GenServer.start_link(__MODULE__, greeting)
  end
  def greet(pid, name) do
    GenServer.call(pid, {:greet, name})
  end
  # server implementation...
          
        

GenEvents

“A GenEvent handler is a process that subscribes to events emitted by a GenEvent manager.”

event handler

          
defmodule SearchIndexer do
  use GenEvent

  def handle_event({:new_entry, entry}, state) do
    # actually do indexing...
    {:ok, state}
  end
end
          
        

Event Manager

          
{:ok, pid} = GenEvent.start_link
GenEvent.add_handler(pid, SearchIndexer, [])

GenEvent.notify(pid, {:new_entry, %{id: 1}})
# GenEvent.ack_notify...
# GenEvent.sync_notify...
          
        

Tasks

“An elixir task is a function that runs in the background.”

Programming Elixir

starting a task

          
worker = Task.async(Mod, :expensive_call, [])
# do other things...
result = Task.await(worker)
          
        

10,000 parallel tasks

          
(for i <- 1..10_000, into: [], do: i)
|> Enum.map(&Task.async(Mod, :expensive_call, [&1]))
|> Enum.map(&Task.await/1)
          
        

Agents

“An agent is a background process that maintains state.”

Programming Elixir

Say it

          
Say.it("Bonjour")
|> Say.it("le")
|> Say.it("monde!")
|> Say.it()

"Bonjour le monde!"
          
        
          
def it(term) when is_binary(term) do
  start_link |> it(term)
end

defp start_link do
  {:ok, agent_pid} = Agent.start_link fn -> [] end
  agent_pid
end
          
        
          
def it(agent_pid, word) do
  Agent.update(agent_pid, &([word|&1]))
  agent_pid
end
          
        
          
def it(term) when is_pid(term) do
  sentence = Agent.get(term, &(&1))
             |> Enum.reverse
             |> Enum.join(" ")
  Agent.stop(term)
  sentence
end
          
        

Supervisors

“A supervisor is a process that is given a list of processes to monitor and is told what to do if a process dies, and how to prevent restart loops.”

Programming Elixir

“Supervisors are the heart of reliability

Programming Elixir

named supervisor

          
defmodule GreeterSupervisor do
  use Supervisor

  def start_link do
    Supervisor.start_link(
      __MODULE__, [], name: __MODULE__)
  end
          
        

supervising another process

          
defmodule GreeterSupervisor do
  use Supervisor
  # def start_link...

  def init(_args) do
    children = [ worker(Greeter, ["Hello"]) ]
    supervise(children, strategy: :one_for_one)
  end
end
          
        
Strategy Description
:one_for_one
When a process crashes, restart it.
:one_for_all
When one process crashes, restart all of them.
:rest_for_all
When one crashes, restart it and all others that depend on it.
:simple_one_for_one
Like :one_for_one but only when dynamically adding children.

Nodes

“Nodes are the basis of distribution

Programming Elixir

Started in the first node…

          
$ iex --sname one greeter.exs
          
          
iex(one@myhost)1> GenServer.start_link(
...(one@myhost)1>   Greeter,
...(one@myhost)1>   "Hello",
...(one@myhost)1>   name: {:global, :greeter})
          
        

…Called from the second.

          
$ iex --sname two
          
          
iex(two@myhost)1> Node.connect :"one@myhost"
true
iex(two@myhost)2> Node.list
[:one@myhost]
iex(two@myhost)3> GenServer.call(
...(two@myhost)3>   {:global, :greeter},
...(two@myhost)3>   {:greet, "World"})
"Hello, World!"
          
        

Started in the second from the first…

          
$ iex --sname one
          
          
iex(one@myhost)1> Node.connect(:"two@myhost")
iex(one@myhost)2> Node.spawn(:"two@myhost",
...(one@myhost)2>   GenServer, :start_link,
...(one@myhost)2>   [Greeter, "Hello",
...(one@myhost)2>     [name: :hello]])
          
        

…Called locally in the second.

          
$ iex --sname two greeter.exs
          
          
iex(two@myhost)1> GenServer.call(:hello,
...(two@myhost)1>   {:greet, "World"})
"Hello, World!"
          
        

last call

Metaprogramming

“The first law of metaprogramming is: you do not use metaprogramming when plainprogramming will suffice.”

quote & unquote

Case study

gsp(html(js(regex(groovy))))

          
<html>
  <head>
    <script>
      var re = /^<%= someExpression() %>\$/;
    </script>
  </head>
</html>
          
        

String templating

          
param = "world"
"Hello, #{param[0].toUpperCase() + param[1..-1]}!"
#=> "Hello, World!"
          
        

Just enough Lisp

          
(+ 1 2) ;=> 3

'(+ 1 2) ;=> (+ 1 2)

`(+ 1 ,(+ 2 2)) ;=> (+ 1 4)

(let ((param 2))
  `(+ 1 ,(expt param 3))) ;=> (+ 1 8)
          
        

quote

          
iex(1)> 1 + 2
3

iex(2)> quote do 1 + 2 end
{:+, [...], [1, 2]}
          
        

unquote

          
param = 2
a_quoted_expr = quote do
  1 + unquote(:math.pow(param, 3))
end
#=> {:+, [...], [1, 8.0]}
Macro.to_string(a_quoted_expr)
#=> "1 + 8.0"
          
        

macros

JSONSchema

          
{ id: 'name', type: 'string' }
{ id: 'age', type: 'integer', minimum: '18' }
{ id: 'contact', type: 'object',
  properties: {
    name: { '$ref': 'name' },
    age: { '$ref': 'age' }
  }
}
          
        

JSONSchema with pattern matching

          
def valid?(:age, candidate)
  when is_integer(candidate)
    and candidate >= 18, do: true

def valid?(:contact, %{"name"=>n, "age"=>a}) do
  valid?(:name, n) and valid?(:age, a)
end
          
        

JSONSchema macros

          
defmodule ContactSchema do
  use JSONSchema

  schema id: :name, type: :string
  schema id: :age, type: :integer, minimum: 18
  schema id: :contact, type: :object, properties:
         %{"age" => :age, "name" => :name}
end
          
        
          
defmodule JSONSchema do
  defmacro schema(opts) do
    do_schema(opts[:type], opts)
  end
  def do_schema(:string, opts) do ... end
  def do_schema(:integer, opts) do ... end
  def do_schema(:object, opts) do ... end
end
          
        
          
def do_schema(:string, opts) do
  quote do
    def valid?(unquote(opts[:id]), candidate)
      when is_binary(candidate), do: true
  end
end
          
        
          
def do_schema(:object, opts) do
  quote do
    def valid?(unquote(opts[:id]), candidate) do
      props = unquote(opts[:properties])
      Enum.all? props, fn {prop_name, sch_id} ->
        valid?(sch_id, candidate[prop_name])
      end
    end
  end
end
          
        

“using”

          
defmodule ContactSchema
  use JSONSchema
end
          
          
defmodule JSONSchema do
  defmacro __using__(opts) do
    quote do
      import unquote(__MODULE__)
    end
  end
end
          
        

Resources

Photos