The Elixir of Life

or how I learned to stop worrying and love FP

Why another language

  • Erlang
    • cool stuff
    • ugly syntax

 

  • Awesome concurrency libraries

 

  • Pretty fast
    • looking at you clojure and scala

 

  • Cool |> (pipe) operator 
 -module(echo).
 -export([start/1,server/1,handle_messages/1]).

 start(Port) ->
     spawn(?MODULE,server,[Port]).

 server(Port) ->
     {ok, Socket} = gen_tcp:listen(Port,[{packet,line}]),
     listen(Socket).

 listen(Socket) ->
     {ok, Active_socket} = gen_tcp:accept(Socket),
     Handler = spawn(?MODULE,handle_messages,[Active_socket]),
     ok = gen_tcp:controlling_process(Active_socket, Handler),
     listen(Socket)

Ugly Erlang

Language Basics

# There are numbers
3    # integer
0x1F # integer
3.0  # float

# Atoms, that are literals, a constant with name. They start with `:`.
:hello # atom

# Tuples that are stored contiguously in memory.
{1,2,3} # tuple

# We can access a tuple element with the `elem` function:
elem({1, 2, 3}, 0) #=> 1

# Lists that are implemented as linked lists.
[1,2,3] # list

# We can access the head and tail of a list as follows:
[head | tail] = [1,2,3]
head #=> 1
tail #=> [2,3]

# Strings and char lists
"hello" # string

# Multi-line strings
"""
I'm a multi-line
string.

Language Basics

# To concatenate lists use `++`
[1,2,3] ++ [4,5]     #=> [1,2,3,4,5]

# To concat strings use <>
"hello " <> "world"  #=> "hello world"

# Maps are key-value pairs
genders = %{"david" => "male", "gillian" => "female"}
genders["david"] #=> "male"

# Maps with atom keys can be used like this
genders = %{david: "male", gillian: "female"}
genders.gillian #=> "female"

#control flow 

# `if` expression
if false do
  "This will never be seen"
else
  "This will"
end

# There's also `unless`
unless true do
  "This will never be seen"
else
  "This will"
end

Language Basics

# `case` allows us to compare a value against many patterns:
case {:one, :two} do
  {:four, :five} ->
    "This won't match"
  {:one, x} ->
    "This will match and bind `x` to `:two` in this clause"
  _ ->
    "This will match any value"
end

# It's common to bind the value to `_` if we don't need it.
# For example, if only the head of a list matters to us:
[head | _] = [1,2,3]
head #=> 1
tail #=> [2,3]

# `cond` lets us check for many conditions at the same time.
# Use `cond` instead of nesting many `if` expressions.
cond do
  1 + 1 == 3 ->
    "I will never be seen"
  2 * 5 == 12 ->
    "Me neither"
  1 + 2 == 3 ->
    "But I will"
end

Language Basics

# You can group several functions into a module. Inside a module use `def`
# to define your functions.
defmodule Math do
  def sum(a, b) do
    a + b
  end

  def square(x) do
    x * x
  end
end

Math.sum(1, 2)  #=> 3
Math.square(3) #=> 9

# Function declarations also support guards and multiple clauses:
defmodule Geometry do
  def area({:rectangle, w, h}) do
    w * h
  end

  def area({:circle, r}) when is_number(r) do
    3.14 * r * r
  end
end

Geometry.area({:rectangle, 2, 3}) #=> 6
Geometry.area({:circle, 3})       #=> 28.25999999999999801048
# Geometry.area({:circle, "not_a_number"})
#=> ** (FunctionClauseError) no function clause matching in Geometry.area/1

Actually doing stuff

  • IEX
    • packaged with Elixir - REPL
  • Mix
    • Task Runner and package manager

Your First Elixir Project

`mix new {project_name}`

Your First Elixir Project

`Mix.exs`

defmodule Example.Mixfile do
  use Mix.Project

  def project do
    [app: :example,
     version: "0.1.0",
     elixir: "~> 1.3",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps()]
  end
  
  def application do
    [applications: [:logger]]
  end

  defp deps do
    []
  end
end

Currently our project does nothing, Lets change that

defmodule Example.Mixfile do
  use Mix.Project

  def project do
    [app: :example,
     version: "0.1.0",
     elixir: "~> 1.3",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps()]
  end

  def application do
    [mod: {Example, []},
     env: [default: :value],
     applications: [:logger]]
  end

  defp deps do
    []
  end
end

Your First Elixir Project

`Example.exs`

defmodule Example do
  use Application

  def start(_type, _args)do
    IO.puts "starting"
    Task.start(fn -> :timer.sleep(1000); IO.puts("done sleeping") end)
  end
en

Something more interesting

  • Lets make an HTTP call to some API
  • We will use the HTTPoison library to help

mix deps.get

defmodule Api do
  use HTTPoison.Base

  def process_url(url) do
  url
end

def process_response_body(body) do
  body
  IO.inspect(body)
  body
end

end
defmodule Example do
  use Application
  use HTTPoison.Base


  def start(_type, _args)do
    IO.puts "starting"
    Api.start
    Api.get!("http://quotesondesign.com/wp-json/posts?filter[orderby]=rand&filter[posts_per_page]=1")
    Task.start(fn -> :timer.sleep(1000); IO.puts("done sleeping") end)
  end
end

Pipe |>

cool way to compsoe

 1..100 |> Enum.filter(fn(i) -> rem(i,2) == 0 end) |> Enum.map(fn(i) -> i+1 end)

                                vs

Enum.map(Enum.filter(1..100, fn(i) -> rem(i,2) == 0 end), fn(i) -> i+1 end)

OTP and Concurrency

aka GenServer

  • Erlang has GenServer for concurrency
  • Elixir wraps this and makes it better
  • Two types of actions
    • handle_call
      • synchronous 
    • handle_cast
      • asynchronous
    • GenServer is a loop that handles one request per iteration passing along an updated state.

A Simple Queue

defmodule SimpleQueue do
  use GenServer

  ### GenServer API

  @doc """
  GenServer.init/1 callback
  """
  def init(state), do: {:ok, state}

  @doc """
  GenServer.handle_call/3 callback
  """
  def handle_call(:dequeue, _from, [value|state]) do
    {:reply, value, state}
  end
  def handle_call(:dequeue, _from, []), do: {:reply, nil, []}

  def handle_call(:queue, _from, state), do: {:reply, state, state}

  ### Client API / Helper methods

  def start_link(state \\ []) do
    GenServer.start_link(__MODULE__, state, name: __MODULE__)
  end

  def queue, do: GenServer.call(__MODULE__, :queue)
  def dequeue, do: GenServer.call(__MODULE__, :dequeue)
end

A Simple Queue

defmodule SimpleQueue do
  use GenServer

  ### GenServer API

  @doc """
  GenServer.init/1 callback
  """
  def init(state), do: {:ok, state}

  @doc """
  GenServer.handle_call/3 callback
  """
  def handle_call(:dequeue, _from, [value|state]) do
    {:reply, value, state}
  end
  def handle_call(:dequeue, _from, []), do: {:reply, nil, []}

  def handle_call(:queue, _from, state), do: {:reply, state, state}

  ### Client API / Helper methods

  def start_link(state \\ []) do
    GenServer.start_link(__MODULE__, state, name: __MODULE__)
  end

  def queue, do: GenServer.call(__MODULE__, :queue)
  def dequeue, do: GenServer.call(__MODULE__, :dequeue)
end
iex> SimpleQueue.start_link([1, 2, 3])
{:ok, #PID<0.90.0>}
iex> SimpleQueue.dequeue
1
iex> SimpleQueue.dequeue
2
iex> SimpleQueue.queue
[3]

Enqueue - Async

defmodule SimpleQueue do
  use GenServer

  ### GenServer API

  @doc """
  GenServer.init/1 callback
  """
  def init(state), do: {:ok, state}

  @doc """
  GenServer.handle_call/3 callback
  """
  def handle_call(:dequeue, _from, [value|state]) do
    {:reply, value, state}
  end
  def handle_call(:dequeue, _from, []), do: {:reply, nil, []}

  def handle_call(:queue, _from, state), do: {:reply, state, state}

  @doc """
  GenServer.handle_cast/2 callback
  """
  def handle_cast({:enqueue, value}, state) do
    {:noreply, state ++ [value]}
  end

  ### Client API / Helper methods

  def start_link(state \\ []) do
    GenServer.start_link(__MODULE__, state, name: __MODULE__)
  end
  def queue, do: GenServer.call(__MODULE__, :queue)
  def enqueue(value), do: GenServer.cast(__MODULE__, {:enqueue, value})
  def dequeue, do: GenServer.call(__MODULE__, :dequeue)
end
iex> SimpleQueue.start_link([1, 2, 3])
{:ok, #PID<0.100.0>}
iex> SimpleQueue.queue
[1, 2, 3]
iex> SimpleQueue.enqueue(20)
:ok
iex> SimpleQueue.queue
[1, 2, 3, 20]

Adding a supervisor

defmodule Example do
  use Application
  use HTTPoison.Base
  import Supervisor.Spec




  def start(_type, _args)do
    IO.puts "starting"
    children = [
    worker(SimpleQueue, [], [name: SimpleQueue])
    ]
    {:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
    SimpleQueue.enqueue(10)
    SimpleQueue.enqueue(20)
    IO.inspect(SimpleQueue.queue)
    IO.inspect(SimpleQueue.dequeue)
    [{_,p,_,_}] = Supervisor.which_children(pid)
    Process.exit(p,:kill)
    :timer.sleep(1000)
    SimpleQueue.enqueue(300)
    SimpleQueue.enqueue(500)
    IO.inspect(SimpleQueue.queue)
    IO.inspect(SimpleQueue.dequeue)
    Task.start(fn -> :timer.sleep(1000); IO.puts("done sleeping") end)
  end
end

FIN

Made with Slides.com