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

also check out: elixirschool.com

watch for Elixir Code School courses!

THANK YOU!!!

Ghost

By Morgan Laco

Ghost

  • 4,145