Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Thinking Functionally with Elixir

Thinking Functionally with Elixir

In this presentation, I share some of my experience learning functional programming in Elixir and how to leverage its features like pure functions, pattern patching, the pipe operator and recursion to write readable and efficient programs for the BEAM.

Carlos Souza

October 20, 2017
Tweet

More Decks by Carlos Souza

Other Decks in Technology

Transcript

  1. Two Rules of Pure Functions #1 - Return value should

    rely 
 entirely on arguments #2 - No side effects
  2. #1 - Return value should rely 
 entirely on arguments

    #2 - No side effects Two Rules of Pure Functions
  3. Pure Functions Pure Functions defmodule Account do def balance(initial, spending)

    do initial - spending end end IO.puts 800 Account.balance(1000, 200)
  4. Pure Functions Invoke function f on data x Invoke function

    g on the return value of function f on data x, and on data y f(x) g(f(x), y) j(i(h(g(f(x),y),z))) Now this is getting silly...
  5. It’s Hard to Read “From the Inside Out” defmodule Account

    do def balance(initial, spending) do interest(discount(initial, 10), 0.1) end def discount(total, amount) do ... end def interest(total, rate) do ... end end
  6. Piping Functions - |> defmodule Account do def balance(initial, spending)

    do discount(initial, 10) |> interest(0.1) end ... end defmodule Account do def balance(initial, spending) do discount(initial, 10) |> interest(0.1) |> format("$") end ... end read from left to righ new functions added to the end
  7. Piping Functions the Elixir style - |> defmodule Account do

    def balance(initial, spending) do initial |> discount(10) |> interest(0.1) |> format("$") ... end end
  8. Reading contents from a CSV file defmodule Budget do def

    list_transactions do File.read("transactions.csv") |> parse_file |> filter |> normalize |> sort |> print end ... end https://github.com/codeschool/budget-elixir
  9. BrookeSam Brooke, Sam Elements from the list on the right...

    ...are a perfect match against those from the list on the left. users = ["Brooke", "Sam"] IO.puts users [first, second] = users IO.puts "#{first}, #{second}" Pattern Matching Lists - [ ]
  10. Pattern Matching Tuples - { } {:ok, content} {:ok, "some

    content"} {:error, :enoent} Reading a file File.read("transactions.csv") =
  11. Pattern Matching in Functions defmodule Budget do def list_transactions do

    |> parse_file end ... end or takes ONE argument {:ok, content} {:error, error} File.read("transactions.csv")
  12. Pattern Matching in Functions No IF statement! defmodule Budget do

    ... 
 def parse_file({:ok, content}) do
 ... end 
 def parse_file({:error, error}) do ... end end
  13. Pattern Matching Maps - %{ } empty variables are populated

    person = %{ "name" => "Brooke", "age" => 42 } = person , "age" => age matches keys on the right %{ "name" => name }
  14. Pattern Matching Portions of Maps - %{ } person =

    %{ "name" => "Brooke", "age" => 42 } only reads portion of the map %{ "name" => name } = person
  15. Pattern Matching Nested Maps - %{ } person = %{

    "name" => "Brooke", "address" => %{ "city" => "Orlando", "state" => "FL"}} %{ "address" => %{ "state" => state }} = person matching on portion of the nested keys
  16. Pattern Matching in Phoenix defmodule FireStarterWeb.VideoController do ... def show(

    ) do video = Repo.get(Video, id) render conn, "show.html", video: video end end Phoenix is MVC Elixir for the web. pattern matching! conn, %{"id" => id}
  17. Recursion "Recently refreshed sourdough, bubbling
 through fermentation: the recipe calls

    for 
 some sourdough left over from the last 
 time the same recipe was made." From https://en.wikipedia.org/wiki/Recursion:
  18. Two Cases for Recursion All recursive functions involve the following

    two cases (or two clauses): 1. The base case or terminating scenario, 
 where the function does NOT invoke itself. 2. The recursive case, where computation 
 happens and the function invokes itself.
  19. Recursive Factorial defmodule MyMath do def factorial(1) do 1 end

    def factorial(n) do n * factorial(n - 1) end end MyMath.factorial(3) #=> 6 MyMath.factorial(4) #=> 24 terminating
 scenario recursive case
  20. Recursion [%{"name" => "Cyle Larin", "position" => "forward"}, %{"name" =>

    "Antonio Nocerino", "position" => "midfielder"}, %{"name" => "Joe Bendik", "position" => "goalkeeper"}, %{"name" => "Jason Kreis", "position" => "coach"}] %{"name" => "Joe Bendik", "position" => "goalkeeper"} Listing players from Orlando City Soccer
  21. Recursion defmodule SoccerTeam do def list_all() do end def get_goalie()

    do end end SoccerTeam.list_all SoccerTeam.get_goalie
  22. Recursion defmodule SoccerTeam do use HTTPoison.Base def list_all() do url

    = "https://..." get_url(url) |> parse end defp parse( ) do end end %{status_code: 200, body: json_response} {:ok, list } = Poison.Parser.parse(json_response) list
  23. Recursion [%{"name" => "Cyle Larin", "position" => "forward"}, %{"name" =>

    "Antonio Nocerino", "position" => "midfielder"}, %{"name" => "Joe Bendik", "position" => "goalkeeper"}, %{"name" => "Jason Kreis", "position" => "coach"}] A list of maps
  24. Recursion defmodule SoccerTeam do use HTTPoison.Base def get_goalie() do url

    = "https://..." get_url(url) |> parse end end |> find_goalie
  25. Recursion defmodule SoccerTeam do ... def find_goalie([%{"position" => "goalkeeper"} =

    head|_]) do head end def find_goalie([_|tail]) do find_goalie(tail) end def find_goalie([]) do raise "Error. No goalie found" end end No for loop No while statement
  26. Recursive Factorial defmodule MyMath do def factorial(1) do 1 end

    def factorial(n) do n * factorial(n - 1) end end MyMath.factorial(10_000_000)
  27. Recursive Factorial defmodule MyMath do def factorial(1) do 1 end

    def factorial(n) do n * factorial(n - 1) end end MyMath.factorial(10_000_000) not the LAST expression in this function
  28. Recursive Factorial defmodule MyMath do def factorial(1) do 1 end

    def factorial(n) do n * factorial(n - 1) end end MyMath.factorial(10_000_000) the LAST expression in this function
  29. Tail Call Optimization MyMath.factorial(10_000_000) defmodule MyMath do def factorial(1, acc)

    do acc end def factorial(n, acc) do factorial(n-1, n * acc) end def factorial(n) do factorial(n, 1) end end
  30. That was a lot! - Functional Programming - Elixir -

    Pure Functions, Pattern Matching and 
 Recursion - Recursion with TCO FTW