Elementary Elixir

A brief look at Elixir

Talk by Trace Helms

What Is Elixir?

Elixir is

  • Built by Rails core member Jose Valim
  • Built on top of the Erlang VM
  • Functional
  • Concurrent
  • Resilient

Ok...Why Do I Care?

Functional

  • No sharing memory
  • Other functions can't mess with your variables
  • Fast garbage collection that doesn't stop the world
  • Side effect: Can't reassign variables (sort of)

Ok...Why Do I Care? (cont.)

Concurrent

  • Lots of tiny lightweight processes
  • Utilize more cores
  • Distribute and utilize more machines

Ok...Why Do I Care? (cont.)

Resilient

  • Monitor processes
  • Let them crash
  • Restart processes when they fail via supervisors

Performance Implications

Phoenix 2 million connections

2 million websocket connections!

On a single server!

Performance Implications

The Language

Pattern Matching

You don't assign variables, you pattern match.

Maybe variables get assigned as a side effect.

Pattern Matching


iex> x = 2
2
iex> y = 3
3
iex> 2 = x
2 # waaaat?
iex> 2 = y
** (MatchError) no match of right hand side value: 3
          

Re-Assigning Variables


iex> x = 2
2
iex> x = 3
3
iex> x
3
iex> ^x = 2 # caret forces no re-binding of variables
** (MatchError) no match of right hand side value: 2
          

Erlang holds onto the original x behind the scenes.

Side note: it's spelled caret.

Immutability

(your variables don't get changed)


iex> list = ["this", "is", "my", "list"]
["this", "is", "my", "list"]
iex> Enum.sort(list)
["is", "list", "my", "this"]
iex> list
["this", "is", "my", "list"]

iex> list = Enum.sort(list)
["is", "list", "my", "this"]
          

You have to reassign list if you want it to stick.

Probably familiar from some Ruby code.

Pattern Matching


iex> {:ok, result} = {:ok, 42}
{:ok, 42}
iex> result
42

iex> {:ok, result} = {:error, 42}
** (MatchError) no match of right hand side value: {:error, 42}

iex> {_, result} = {:whatever, 42}
{:whatever, 42}
          

Pattern Matching In Functions


defmodule Fibonacci do

  def fib(0), do: 1
  def fib(1), do: 1
  def fib(n), do: fib(n - 2) + fib(n - 1)

end
          

Note: no if statements.

This code is runnable in the /code directory.

Pipe Character

  • Cleans up your code
  • Similar to Unix pipe character
  • Puts the output of the last thing as the first argument to the next thing

Pipe Character

Cleans up your code


# normal way
process(parse_args(args))

# elixir way
args
|> parse_args
|> process
          

Pipe Character On Crack


def process({user, project, count}) do
  Issues.GithubIssues.fetch(user, project)
  |> decode_response
  |> convert_to_list_of_hashdicts
  |> sort_into_ascending_order
  |> Enum.take(count)
  |> print_table_for_columns(["number", "created_at", "title"])
end
          

Recursion

Uh oh...

Let's start with Lists

  • Every list is a linked list
  • Every list can be split into the first element and the rest of the list
  • The first element is the head
  • The rest of the list is the tail
  • The last element of a list is always an empty list

Lists


iex> [head | tail] = [1, 2, 3, 4]
[1, 2, 3, 4]
iex> head
1
iex> tail
[2, 3, 4]
          

iex> [head | tail] = [1]
[1]
iex> head
1
iex> tail
[]
          

Lists


defmodule MyList do

  def square([]), do: []

  def square([head | tail]) do
    [head * head | square(tail) ]
  end

end
          
This code is runnable in the /code directory.

Enum Library

  • You don't need to do a ton of recursion, that's just how it works under the hood.
  • Elixir has all of your favorite functions for enumerables.
  • map, uniq, empty?, count, max, sort, etc...

Concurrency

Processes

  • Lots of lightweight processes
  • Used for concurrency
  • Each process has its own memory
  • Communicate by passing messages

Processes

Let's prove that they're lightweight.

Let's fire off a million processes.

Processes Example


defmodule Processes do
  def counter(next_pid) do
    receive do
      n ->
        send(next_pid, n+1)
    end
  end

  def create_processes(n) do
    last = Enum.reduce(1..n, self, fn(_, send_to) ->
      spawn(Processes, :counter, [send_to])
    end)

    send last, 0

    receive do
      final_answer when is_integer(final_answer) ->
        "Result is #{inspect(final_answer)}"
    end
  end

  def run(n) do
    IO.puts inspect :timer.tc(Processes, :create_processes, [n])
  end
end
          
This code is runnable in the /code directory.

Processes Example


$ iex processes.exs
iex> Processes.run(200_000)
{1456993, "Result is 200000"}
:ok
iex> Processes.run(300_000)
** (SystemLimitError) a system limit has been reached

13:07:26.075 [error] Too many processes
          
First element of tuple is elapsed time in microseconds. So 1.45 seconds.

Processes Example

Let's go for 1,000,000! Tell Erlang to prepare itself...


$ elixir --erl "+P 1000000"  -r processes.exs -e "Processes.run(1_000_000)"
          

Processes Example

A million processes...in under 8 seconds...on my MacBook.


{7857020, "Result is 1000000"}
          

"Micro Services"

  • Elixir has "Applications"
  • Each application runs in its own process and is supervised.
  • You get benefits of a monolithic app and of microservices.

"Micro Services"

Let's see them with observer!


$ iex
iex> :observer.start
          

Distribution Over Nodes

  • In Elixir, you can distribute your code by connecting a new "node".
  • This can be another shell window, computer, or a server in Hong Kong.
  • Node communication is built in.

Distribution Example

I'm going to calculate a Fibonacci number...

On one of your machines...

From my machine.

Distribution Example Steps

  • Each computer pulls down the code I'm going to run.
  • Each computer starts an iex session with a node name and cookie.
  • Each computer loads the code into the session.
  • I'll connect to the machine over WiFi.
  • I'll call a function that spawns a process on the remote machine.
  • That function will listen for the answer and display it when it's done.

Distribution Example

Each Machine


$ ifconfig # get ip address from en0
...
$ iex --name node_name@your_ip_address --cookie cookie_name
iex> c("dist_fib.exs")
[Fibonacci]
          
Fire up Activity Monitor to see your CPU working.

Distribution Example

My Machine


iex> Node.connect(:"nodetwo@10.0.1.24")
true
iex> Fibonacci.spawn_fib(:"nodetwo@10.0.1.24", 5)
The result is 8
:ok
iex> Fibonacci.spawn_fib(:"nodetwo@10.0.1.24", 45)
...
          

Distribution Code

This code is available in the repo to review.

Installing Elixir


$ brew install elixir
          

http://elixir-lang.org/

http://www.phoenixframework.org/

Credits

Most of these examples came from the book Programming Elixir by Dave Thomas (the Prag Prog guy).

Questions?