Talk by Trace Helms
Elixir is
2 million websocket connections!
On a single server!
You don't assign variables, you pattern match.
Maybe variables get assigned as a side effect.
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
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.
(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.
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}
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.
Cleans up your code
# normal way
process(parse_args(args))
# elixir way
args
|> parse_args
|> process
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
Uh oh...
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
[]
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.
Let's prove that they're lightweight.
Let's fire off a million processes.
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.
$ 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.
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)"
A million processes...in under 8 seconds...on my MacBook.
{7857020, "Result is 1000000"}
Let's see them with observer!
$ iex
iex> :observer.start
I'm going to calculate a Fibonacci number...
On one of your machines...
From my 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.
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)
...
This code is available in the repo to review.
$ brew install elixir
Most of these examples came from the book Programming Elixir by Dave Thomas (the Prag Prog guy).