Giving up the Object-Oriented Ghost
@morganlaco
github.com/mlaco
hello@morganlaco.com
http://slides.com/morganlaco/deck-2/live
Ghosts
Ghosts
- Objects
- Mutability
- Conditionals
- Loops
What is functional programming?
Functional vs Imperative
Lambda Calculus
Lambda Calculus
2 ≡ λf.λx.f (f x)
+ ≡ λm.λn.λf.λx.m f (n f n)
Lambda Calculus
2 ≡ λf.λx.f (f x)
fn(f) {
return fn(x) {
return f(f(x))
}}
Lambda Calculus
2 ≡ λf.λx.f (f x)
fn(f) {
return fn(x) {
return f(f(x))
}}
Ghost 1: Objects
The Fours Awakens
class ConnectFour::Game
def initialize
@board = ConnectFour::Board.new
# other instance variables
end
def play
while continue_game
get_move
handle_move
end
display_result
end
# ...
end
ConnectFour::Game.new.play
defmodule ConnectFour do
def start(_type, _args) do
ConnectFour.initial_state
|> do_turn
end
def do_turn(state) do
ConnectFour.IO.display_board(state[:board])
case ConnectFour.Board.update_board(state[:board], \
move, state[:turn]) do
# ... error and quit cases ...
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
Objects ->
Functional Purity
Ghost 2: Conditionals
def find_top(col, n\\0) do
col_list = Tuple.to_list(col)
[h|t] = col_list
cond do
!Enum.any?(t) && h != 0 ->
{:error}
h == 0 ->
{:ok, n}
true ->
List.to_tuple(t) |> find_top(n+1)
end
end
0
1
-1
Key
#
def find_top(col, n\\0) do
col_list = Tuple.to_list(col)
[h|t] = col_list
cond do
!Enum.any?(t) && h != 0 ->
{:error}
h == 0 ->
{:ok, n}
true ->
List.to_tuple(t) |> find_top(n+1)
end
end
def find_top(col, n\\0) do
col_list = Tuple.to_list(col)
[h|t] = col_list
cond do
!Enum.any?(t) && h != 0 ->
{:error}
h == 0 ->
{:ok, n}
true ->
List.to_tuple(t) |> find_top(n+1)
end
end
h
def find_top(col, n\\0) do
col_list = Tuple.to_list(col)
[h|t] = col_list
cond do
!Enum.any?(t) && h != 0 ->
{:error}
h == 0 ->
{:ok, n}
true ->
List.to_tuple(t) |> find_top(n+1)
end
end
h
t
def find_top(col, n\\0) do
col_list = Tuple.to_list(col)
[h|t] = col_list
cond do
!Enum.any?(t) && h != 0 ->
{:error}
h == 0 ->
{:ok, n}
true ->
List.to_tuple(t) |> find_top(n+1)
end
end
h
t
Pattern Matching
Pattern Matching
[h|t] = col_list
def check(remaining, []) do
[next_row | other_rows] = remaining
check(other_rows, next_row)
end
def check([], row) do
check_row(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
Inversion of Control
Conditionals ->
Pattern Matching
Ghost 3: Mutability
Mutability
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
Why variables can be re-bound
Benefits of Immutability
- Performance
- Distribution
FP in other languages
http://www.lispcast.com/imperative-mindset
Mutability ->
Immutability
Ghost 4: Loops
Loops
(1..100).each do |i|
do_something(i)
end
while i_am_talking do
be_nervous
end
No Loops - why tho?
Recursion
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
Recursion
def self.check(grid)
grid.each_with_index do |column, column_index|
n = 1
last = 0
column.each_with_index do |cell, row_index|
if cell == last && cell != 0
n += 1
else
n = 1
end
last = cell
return true if n == 4
end
end
def check_horizontal(board) do
Transpose.transpose(board)
|> check([])
end
def check(remaining, []) do
[next_row | other_rows] = remaining
check(other_rows, next_row)
end
def check([], row) do
check_row(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
def check([next_row | other_rows], []) do
check(other_rows, next_row)
end
# Equivalent to:
def check(remaining, []) do
[next_row | other_rows] = remaining
check(other_rows, next_row)
end
# pass
def check_row(cell, [], last, matching) do
new_matching =
if cell == last do
matching + 1
else
1
end
new_matching == 4
end
# pass
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 | other_cells] = remaining
check_row(next_cell, other_cells, cell, new_matching)
end
end
Tail Recursion
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
What I told you was true...
Loops
Enum.reduce( (1..100), \
fn(x, acc) -> x + acc end )
# => 5050
Enum.map( (1..5), \
fn(x) -> 2 * x end )
# => [2, 4, 6, 8, 10]
Enum.filter( (1..5), \
fn(x) -> rem(x, 2) == 0 end )
# => [2, 4]
defmodule ConnectFour.WinChecker.OrthogonalHigherOrder do
def check_horizontal(board) do
Enum.filter(board, fn(row) -> eval_row(row) end)
|> Enum.any?(fn(res) -> res end)
end
# If the row has 4 consecutive,
# return [n: 4, prev: c] where c is the winning
# color
def eval_row(row) do
Enum.reduce(row, [n: 0, prev: 0], \
fn(cell, acc) -> eval_cell(acc, cell) end)
|> is_there_a_win
end
def is_there_a_win([n: 4, prev: _]) do
true
end
def is_there_a_win(_) do
false
end
# If a match has already been found,
# just pass that along
def eval_cell([n: 4, prev: a], _) do
[n: 4, prev: a]
end
# param
# dict acc
# n: current number of accumulated consecutive cells
# prev: color of previous cell
# param
# cell: current cell color
def eval_cell(acc, cell) do
[n: next_n(acc, cell), prev: cell]
end
# update the number of consecutive same-colored cells
def next_n(_, 0) do
# Reset the count
0
end
def next_n([n: n, prev: 0], a) do
# First of the color
1
end
def next_n([n: n, prev: a], a) do
# Matches with previous
n + 1
end
def next_n([n: _, prev: a], b) do
# No match, reset
0
end
end
Loops ->
Recursion
Ghosts Review
Objects
Functional Purity
Mutability
Immutability
elsif
Pattern Matching
Loops
Recursion
references
- From Imperative to Functional and Back-Monads are for Functional Languages
- From Imperative to Functional: How to Make the Leap
- A Practical Introduction to Functional Programming
- StackOverflow: What are the core concepts in functional programming?
- Elixir Getting Started Guide
- Destroy All Ifs
- 16 Months of Functional Programming
- Learn You Haskell
- Is object immutability in functional programming inherently performance intensive?
- How to Switch from the Imperative Mindset
- Functional Programming Techniques With Ruby: Part I
- From Imperative to Functional Programming (for Absolute Beginners)
- Transposing a Matrix: Thinking Recursively in Elixir
- Don’t Be Scared Of Functional Programming
- Myth of the Day: Functional Programmers Don't Use Loops
also check out: elixirschool.com
watch for Elixir Code School courses!
THANK YOU!!!
Ghost
By Morgan Laco
Ghost
- 4,145