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.
- handle_call
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
elixir
By Kyle Potts
elixir
- 227