Elixir for Object-Oriented Developers
@morganlaco
rails conf
elixir
why elixir?
scalability
- Lightweight threads
- ~10^5 concurrent processes
- distribution
fault tolerance
- Supervisors
functional programming
- Fast
- Maintainable
great error messages
performance
case study
before
- 150 AWS instances
- Log-jammed responses
- multiple engineers per app
- multiple complex caching strategies
after
- 1/15th servers
- 10-30ms avg response
(Hella fast!!!) - Largest avg spike 400 ms
- Largest outlier 800ms
- ~1 engineer/app
- no caching
How?
how?
1. Elixir faster at GC
how?
2. Elixir consumes all CPU cores
how?
3. Elixir is a compiled language
how?
4. phoenix makes smart use of the BEAM
how?
5. Database is not slow
Watch brian cardarella's talk!
original
content
warning
the fours awakens
Ruby
class ConnectFour::Game
def play
while continue_game
get_move
handle_move
end
display_result
end
# ...
end
ConnectFour::Game.new.play
elixir
defmodule ConnectFour do
def start(_type, _args) do
state = ConnectFour.initial_state
do_turn(state)
end
def do_turn(state) do
ConnectFour.IO.display_board(state[:board])
res = with \
{:ok, move} <- ConnectFour.IO.get_move(state[:turn]),
{:ok, new_board} <- \
ConnectFour.Board.update_board(state[:board], move, state[:turn]),
do:
new_state(state, new_board)
case res do
# ...
new_state ->
if ConnectFour.WinChecker.check(new_state[:board]) do
ConnectFour.IO.do_win(new_state)
else
do_turn(new_state)
end
end
end
ConnectFour.start
tail recursion
tail recursion
# Ruby
array = [1, 2, 3]
(1..3).each do |i|
array[i] = array[i] * 2
end
# Elixir
def do_fun_things(x) do
# Do them thangs
do_fun_things(y)
# ^-- Last thing
end
What is it?
tail recursion
how does it work?
def fibonacci(n) do
cond do
n == 0
0
n == 1
1
true ->
fibonacci(n, 0, 1)
end
end
def fibonacci(n, a, b) do
if n <= 0 do
a
else
fibonacci(n - 1, b, a + b)
end
end
n | a | b |
---|---|---|
4 | - | - |
4 | 0 | 1 |
3 | 1 | 1 |
2 | 1 | 2 |
1 | 2 | 3 |
0 | 3 | 5 |
tail recursion
how does it work?
fibonacci(n)
fibonacci(n)
fibonacci(n)
fibonacci(n)
<process>
def fibonacci(n) do
cond do
n == 0
0
n == 1
1
true ->
fibonacci(n, 0, 1)
end
end
def fibonacci(n, a, b) do
if n <= 0 do
a
else
fibonacci(n - 1, b, a + b)
end
end
tail recursion
how does it work?
fibonacci(n)
fibonacci(n)
fibonacci(n)
fibonacci(n)
<process>
def fibonacci(n) do
cond do
n == 0
0
n == 1
1
true ->
fibonacci(n, 0, 1)
end
end
def fibonacci(n, a, b) do
if n <= 0 do
a
else
fibonacci(n - 1, b, a + b)
end
end
audience
participation
alert
immutable data
snokes_true_identity = "Plagueis"
# => "Plagueis"
snokes_true_identity = "Leia"
# => What happens?
immutability
snokes_true_identity = "Plagueis"
# => "Plagueis"
snokes_true_identity = "Leia"
# => "Leia"
immutability
what the fuzz?
immutability
before
after
foo
foo
foo = 4
foo = 5
4
5
immutability
before
after
foo
foo
foo = 4
foo = 5
4
5
4
foo
immutability
- Functional programming
- thread safe
syntax
pipes!
pipes!
defmodule ConnectFour.IO do
def get_move(turn) do
validate_move(
String.trim(
IO.gets(:stdio, "[#{translate_player(turn)}] Enter move: "
)))
end
def validate_move(move) do
cond do
String.match?(move, ~r/[1-6]/) ->
{:ok, String.to_integer(move) - 1}
String.match?(move, ~r/q(uit)?/) ->
{:quit}
true ->
{:error}
end
end
end
pipes!
defmodule ConnectFour.IO do
def get_move(turn) do
IO.gets(:stdio, "[#{translate_player(turn)}] Enter move: ")
|> String.trim
|> validate_move
end
def validate_move(move) do
cond do
String.match?(move, ~r/[1-6]/) ->
{:ok, String.to_integer(move) - 1}
String.match?(move, ~r/q(uit)?/) ->
{:quit}
true ->
{:error}
end
end
end
pipes!
def insert_at_end(list, item) do
Enum.reverse(list)
|> Enum.into(list, [item])
|> Enum.reverse(list)
end
pipes!
def insert_at_end(list, item) do
Enum.reverse(list)
|> Enum.into([item])
|> Enum.reverse
end
pipes!
defmodule ConnectFour.IO do
# ...
def display_board(board) do
# Bad!
[h|t] = Listify.listify(board)
|> Antitranspose.antitranspose
display_board(h, t)
IO.puts " 1 2 3 4 5 6"
end
# ...
end
pipes!
defmodule ConnectFour.IO do
# ...
def display_board(board) do
# Good!
[h|t] = prepare_board(board)
display_board(h, t)
IO.puts " 1 2 3 4 5 6"
end
def prepare_board(board) do
Listify.listify(board)
|> Antitranspose.antitranspose
end
# ...
end
with
case get_move(state[:turn]) do
{:ok, move} ->
case ConnectFour.Board.update_board(state[:board],\
move, state[:turn]) do
{:ok, new_board} ->
new_state(state, new_board)
|> do_turn
{:error} ->
do_turn(state) # redundant; can we use `with`?
{:error} ->
# do error stuff
IO.puts("Invalid move")
do_turn(state)
end
with
res = with \
{:ok, move} <- get_move(state[:turn]),
{:ok, new_board} <- \
ConnectFour.Board.update_board(state[:board], \
move, state[:turn]),
do:
new_state(state, new_board)
|> do_turn
case res do
{:error} ->
IO.puts "error!"
end
function isHorizontal(grid) {
for (let x = 0; x < rowsNum; x++) {
for (let y = 0; y < columnsNum; y++) {
// Current piece in this row
let piece = grid[y][x];
// Reset things if piece is 0
if (piece === 0) {
found = 0;
foundPiece = 0;
continue;
}
if (piece !== foundPiece) {
found = 1;
foundPiece = piece;
continue;
}
found++;
if (found >= 4) {
return true;
}
}
}
return false; // nothing was found in the same row
}
defmodule ConnectFour.WinChecker.Orthogonal do
def check_vertical(board) do
check(board, [])
end
def check([], row) do
check_row(row)
end
def check(remaining, []) do
[next_row | other_rows] = remaining
check(other_rows, next_row)
end
def check(remaining, row) do
if check_row(row) do
true
else
[next_row | other_rows] = remaining
check(other_rows, next_row)
end
end
end
defmodule ConnectFour.WinChecker.Orthogonal do
def check_row(row) do
[cell | other_cells] = row
check_row(cell, other_cells, 0, 0)
end
def check_row(cell, remaining, last, matching) do
new_matching =
if cell == last && cell != 0 do
matching + 1
else
1
end
if new_matching == 4 do
true
else
[next_cell | others] = remaining
check_row(next_cell, others, cell, new_matching)
end
end
end
defmodule ConnectFour.WinChecker.Orthogonal do
def check_row(cell, [], last, matching) do
new_matching =
if cell == last do
matching + 1
else
1
end
new_matching == 4
end
end
# Using accumulators in recursive fns
# http://langintro.com/elixir/article2/
resources
thank you!
Elixir for OO Developers (ODev Edition)
By Morgan Laco
Elixir for OO Developers (ODev Edition)
- 1,533